/* Copyright (c) Citrix Systems Inc. * All rights reserved. * * Redistribution and use in source and binary forms, * with or without modification, are permitted provided * that the following conditions are met: * * * Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ using System; using System.Collections.Generic; using System.ComponentModel; using System.Configuration; using System.Diagnostics; using System.Globalization; using System.IO; using System.Net; using System.Reflection; using System.Windows.Forms; using System.Threading; using System.Drawing; using XenAdmin.Core; using XenAdmin.Network; using XenAdmin.Dialogs; using XenAdmin.XenSearch; using XenAPI; using System.Runtime.InteropServices; using XenAdmin.Network.StorageLink; using XenAdmin.Actions; namespace XenAdmin { public static class Program { public const int DEFAULT_WLB_PORT = 8012; /// /// A UUID for the current instance of XenCenter. Used to identify our own task instances. /// public static readonly string XenCenterUUID = Guid.NewGuid().ToString(); private static NamedPipes.Pipe pipe; private const string PIPE_PATH_PATTERN = @"\\.\pipe\XenCenter-{0}-{1}-{2}"; /// /// Color.Transparent on most platforms; SystemColors.Window when ClearType is enabled. /// This is to work around a bug in TextRenderer.DrawText which causes text written to a double-buffer /// using ClearType to anti-alias onto black rather than onto the background colour. In this case, /// we use Window, because by luck those labels are always on top of that colour on Vista and XP. /// We indicate that we're writing to a buffer (rather than the screen) by setting Graphics.TextContrast /// to 5 (the default is 4). This hack was needed because there's no easy way to add info to /// a Graphics object. (CA-22938). /// public static Color TransparentUsually = Color.Transparent; public static readonly Color ErrorForeColor = Color.White; public static readonly Color ErrorBackColor = Color.Firebrick; public static Font DefaultFont = FormFontFixer.DefaultFont; public static Font DefaultFontBold; public static Font DefaultFontUnderline; public static Font DefaultFontBoldUnderline; public static Font DefaultFontItalic; public static Font DefaultFontHeader; // Also set in Main() AFTER we call EnableVisualStyles(). // We set them here only so something decent shows up in the Designer. public static Color TitleBarStartColor = ProfessionalColors.OverflowButtonGradientBegin; public static Color TitleBarEndColor = ProfessionalColors.OverflowButtonGradientEnd; public static Color TitleBarBorderColor = TitleBarEndColor; public static Color TitleBarForeColor = Color.White; public static Color HeaderGradientStartColor = Color.FromArgb(57, 109, 140); public static Color HeaderGradientEndColor = Color.FromArgb(63, 139, 137); public static Color HeaderGradientForeColor = Color.White; public static Font HeaderGradientFont = new Font(DefaultFont.FontFamily, 11.25f); public static Font HeaderGradientFontSmall = DefaultFont; public static Font TabbedDialogHeaderFont = HeaderGradientFont; public static Color TabPageRowBorder = Color.DarkGray; public static Color TabPageRowHeader = Color.Silver; public static readonly XenAdmin.Core.PropertyManager PropertyManager = new XenAdmin.Core.PropertyManager(); public static MainWindow MainWindow = null; private static StorageLinkConnectionManager _storageLinkConnections; public static StorageLinkConnectionManager StorageLinkConnections { get { if (_storageLinkConnections == null) { _storageLinkConnections = new StorageLinkConnectionManager(() => MainWindow, ConnectionsManager.XenConnections, ConnectionsManager.ConnectionsLock); } return _storageLinkConnections; } } public static CollectionChangeEventHandler ProgramInvokeHandler(CollectionChangeEventHandler handler) { return delegate(object s, CollectionChangeEventArgs args) { if (Program.MainWindow != null) { Program.BeginInvoke(Program.MainWindow, delegate() { if (handler != null) handler(s, args); }); } }; } /// /// The secure hash of the master password used to load the client session. /// If this is null then no prior session existed and the user should be prompted /// to save his session when the UI is quit. /// public static byte[] MasterPassword = null; /// /// A true value here indicates the user does not want to save session information for this /// particular instance of the UI; but when the UI is restarted he should be prompted again. /// public static bool SkipSessionSave = false; public static bool RunInAutomatedTestMode = false; public static string TestExceptionString = null; // an exception passed back to the test framework private static log4net.ILog log = null; public static volatile bool Exiting = false; public static readonly string AssemblyDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); public static readonly DateTime ApplicationStartTime = DateTime.Now; private static readonly System.Threading.Timer dailyTimer; public static bool IsThemed = false; [DllImport("uxtheme", ExactSpelling = true)] public extern static int IsAppThemed(); static Program() { XenAdminConfigManager.Provider = new WinformsXenAdminConfigProvider(); // Start timer to record resource usage every 24hrs dailyTimer = new System.Threading.Timer((TimerCallback)delegate(object state) { Program.logApplicationStats(); }, null, new TimeSpan(24, 0, 0), new TimeSpan(24, 0, 0)); log4net.Config.XmlConfigurator.ConfigureAndWatch(new FileInfo(Assembly.GetCallingAssembly().Location + ".config")); log = log4net.LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); IsThemed = IsAppThemed() > 0 ? true : false; SetDefaultFonts(); } /// /// The main entry point for the application. /// [STAThread] static public void Main(string[] Args) { //Upgrade settings System.Reflection.Assembly a = System.Reflection.Assembly.GetExecutingAssembly(); Version appVersion = a.GetName().Version; string appVersionString = appVersion.ToString(); log.DebugFormat("Application version of new settings {0}", appVersionString); try { if (Properties.Settings.Default.ApplicationVersion != appVersion.ToString()) { log.Debug("Upgrading settings..."); Properties.Settings.Default.Upgrade(); Properties.Settings.Default.ApplicationVersion = appVersionString; } } catch (ConfigurationErrorsException ex) { log.Error("Could not load settings.", ex); var msg = string.Format("{0}\n\n{1}", Messages.MESSAGEBOX_LOAD_CORRUPTED_TITLE, string.Format(Messages.MESSAGEBOX_LOAD_CORRUPTED, Settings.GetUserConfigPath())); var dlog = new ThreeButtonDialog(new ThreeButtonDialog.Details(SystemIcons.Error,msg,Messages.XENCENTER)) { StartPosition = FormStartPosition.CenterScreen, //For reasons I do not fully comprehend at the moment, the runtime //overrides the above StartPosition with WindowsDefaultPosition if //ShowInTaskbar is false. However it's a good idea anyway to show it //in the taskbar since the main form is not launcched at this point. ShowInTaskbar = true }; dlog.ShowDialog(); Application.Exit(); return; } // Reset statics, because XenAdminTests likes to call Main() twice. TestExceptionString = null; Exiting = false; // Clear XenConnections and History so static classes like OtherConfigAndTagsWatcher // listening to changes still work when Main is called more than once. ConnectionsManager.XenConnections.Clear(); ConnectionsManager.History.Clear(); Search.InitSearch(() => Program.StorageLinkConnections.GetCopy()); TreeSearch.InitSearch(); ArgType argType = ArgType.None; AppDomain.CurrentDomain.UnhandledException -= CurrentDomain_UnhandledException; AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; Application.ThreadException -= Application_ThreadException; Application.ThreadException += Application_ThreadException; Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); try { if (SystemInformation.FontSmoothingType == 2) // ClearType TransparentUsually = SystemColors.Window; } catch (NotSupportedException) { // Leave TransparentUsually == Color.Transparent. This is an old platform // without FontSmoothingType support. } switch (Environment.OSVersion.Version.Major) { case 6: // Vista, 2K8, Win7. if (Application.RenderWithVisualStyles) { // Vista, Win7 with styles. TitleBarStartColor = Color.FromArgb(242, 242, 242); TitleBarEndColor = Color.FromArgb(207, 207, 207); TitleBarBorderColor = Color.FromArgb(160, 160, 160); TitleBarForeColor = Color.FromArgb(60, 60, 60); HeaderGradientForeColor = Color.White; HeaderGradientFont = new Font(DefaultFont.FontFamily, 11.25f); HeaderGradientFontSmall = DefaultFont; TabbedDialogHeaderFont = HeaderGradientFont; TabPageRowBorder = Color.Gainsboro; TabPageRowHeader = Color.WhiteSmoke; } else { // 2K8, and Vista, Win7 without styles. TitleBarForeColor = SystemColors.ControlText; HeaderGradientForeColor = SystemColors.ControlText; HeaderGradientFont = new Font(DefaultFont.FontFamily, DefaultFont.Size + 1f, FontStyle.Bold); HeaderGradientFontSmall = DefaultFontBold; TabbedDialogHeaderFont = HeaderGradientFont; TabPageRowBorder = Color.DarkGray; TabPageRowHeader = Color.Silver; } break; default: TitleBarStartColor = ProfessionalColors.OverflowButtonGradientBegin; TitleBarEndColor = ProfessionalColors.OverflowButtonGradientEnd; TitleBarBorderColor = TitleBarEndColor; TitleBarForeColor = Application.RenderWithVisualStyles ? Color.White : SystemColors.ControlText; HeaderGradientForeColor = TitleBarForeColor; HeaderGradientFont = new Font(DefaultFont.FontFamily, DefaultFont.Size + 1f, FontStyle.Bold); HeaderGradientFontSmall = DefaultFontBold; TabbedDialogHeaderFont = new Font(DefaultFont.FontFamily, DefaultFont.Size + 1.75f, FontStyle.Bold); TabPageRowBorder = Color.DarkGray; TabPageRowHeader = Color.Silver; break; } // Force the current culture, to make the layout the same whatever the culture of the underlying OS (CA-46983). Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture = new CultureInfo(InvisibleMessages.LOCALE, false); if (string.IsNullOrEmpty(Thread.CurrentThread.Name)) Thread.CurrentThread.Name = "Main program thread"; ServicePointManager.DefaultConnectionLimit = 20; ServicePointManager.ServerCertificateValidationCallback = SSL.ValidateServerCertificate; XenAPI.Session.UserAgent = string.Format("XenCenter/{0}", ClientVersion()); ReconfigureConnectionSettings(); log.Info("Application started"); logSystemDetails(); OptionsDialog.Log(); if (Args.Length > 0) log.InfoFormat("Args[0]: {0}", Args[0]); List sanitizedArgs = new List(Args); // Remove the '--wait' argument, which may have been passed to the splash screen sanitizedArgs.Remove("--wait"); string[] args = null; if (sanitizedArgs.Count > 1) { argType = ParseFileArgs(sanitizedArgs, out args); if (argType == ArgType.Passwords) { log.DebugFormat("Handling password request using '{0}'", args[0]); try { PasswordsRequest.HandleRequest(args[0]); } catch (Exception exn) { log.Fatal(exn, exn); } Application.Exit(); return; } } else if (sanitizedArgs.Count == 1 && sanitizedArgs[0] == "messageboxtest") { new Dialogs.MessageBoxTest().ShowDialog(); Application.Exit(); return; } else if (sanitizedArgs.Count > 0) { log.Warn("Unrecognised command line options"); } try { ConnectPipe(); } catch (System.ComponentModel.Win32Exception exn) { log.Error("Creating named pipe failed. Continuing to launch XenCenter.", exn); } Application.ApplicationExit -= Application_ApplicationExit; Application.ApplicationExit += Application_ApplicationExit; MainWindow mainWindow = new MainWindow(argType, args); Application.Run(mainWindow); log.Info("Application main thread exited"); } private static ArgType ParseFileArgs(List args, out string[] filePath) { filePath = new string[args.Count - 1]; for (int i = 0; i < filePath.Length; i++) filePath[i] = args[i + 1]; switch (args[0]) { case "import": return ArgType.Import; case "license": return ArgType.License; case "restore": return ArgType.Restore; case "update": return ArgType.Update; case "xensearch": return ArgType.XenSearch; case "passwords": return ArgType.Passwords; case "connect": return ArgType.Connect; default: log.Warn("Unrecognised command line options"); return ArgType.None; } } /// /// Connects to the XenCenter named pipe. If the pipe didn't already exist, a new thread is started /// that listens for incoming data on the pipe (from new invocations of XenCenter) and deals /// with the command line arguments of those instances. If the pipe does exist, a Win32Exception is thrown. /// /// If creating the pipe failed for any reason. private static void ConnectPipe() { string pipe_path = string.Format(PIPE_PATH_PATTERN, Process.GetCurrentProcess().SessionId, Environment.UserName, Assembly.GetExecutingAssembly().Location.Replace('\\', '-')); // Pipe path must be limited to 256 characters in length if (pipe_path.Length > 256) { pipe_path = pipe_path.Substring(0, 256); } log.InfoFormat(@"Connecting to pipe '{0}'", pipe_path); // Line below may throw Win32Exception pipe = new NamedPipes.Pipe(pipe_path); log.InfoFormat(@"Successfully created pipe '{0}' - proceeding to launch XenCenter", pipe_path); pipe.Read += new EventHandler(delegate(object sender, NamedPipes.PipeReadEventArgs e) { MainWindow m = Program.MainWindow; if (m == null || RunInAutomatedTestMode) { return; } Program.Invoke(m, delegate { log.InfoFormat(@"Received data from pipe '{0}': {1}", pipe_path, e.Message); ArgType argType = ArgType.None; string[] other_args; string file_arg = ""; string[] bits = e.Message.Split(new char[] { ' ' }, 3); if (bits.Length == 2) { List args = new List(); args.Add(bits[0]); args.Add(bits[1]); argType = ParseFileArgs(args, out other_args); file_arg = other_args[0]; if (argType == ArgType.Passwords) { log.Error("Refusing to accept passwords request down pipe. Use XenCenterMain.exe directly"); return; } else if (argType == ArgType.Connect) { log.Error("Connect not supported down pipe. Use XenCenterMain.exe directly"); return; } else if (argType == ArgType.None) { return; } } else if (e.Message.Length > 0) { log.Error("Could not process data received from pipe."); return; } m.WindowState = FormWindowState.Normal; // Note slight hack: the C++ splash screen passes its command line as a // literal string. This means we will get an e.Message e.g. // open "C:\Documents and Settings\foo.xva" // INCLUDING double quotes. Thus we trim the quotes below. if (file_arg.Length > 1 && file_arg.StartsWith("\"") && file_arg.EndsWith("\"")) { file_arg = file_arg.Substring(1, file_arg.Length - 2); } m.ProcessCommand(argType, new string[] { file_arg }); }); }); pipe.BeginRead(); // We created the pipe successfully - i.e. nobody was listening, so go ahead and start XenCenter } internal static void DisconnectPipe() { if (pipe != null) { log.Debug("Disconnecting from named pipe in Program.DisconnectPipe()"); System.Threading.ThreadPool.QueueUserWorkItem((WaitCallback)delegate(object o) { pipe.Disconnect(); }); } } private static void logSystemDetails() { log.Info("Version: " + Assembly.GetExecutingAssembly().GetName().Version.ToString()); log.Info(".NET runtime version: " + Environment.Version.ToString(4)); log.Info("OS version: " + Environment.OSVersion.ToString()); log.Info("UI Culture: " + Thread.CurrentThread.CurrentUICulture.EnglishName); log.Info(string.Format("Bitness: {0}-bit", IntPtr.Size * 8)); } static void Application_ApplicationExit(object sender, EventArgs e) { Exiting = true; logApplicationStats(); Clip.UnregisterClipboardViewer(); try { // Lets save the connections first, so we can save their connected state Settings.SaveIfRequired(); } catch (Exception) { // Ignore here } // The application is about to exit - gracefully close connections to // avoid a bunch of WinForms related race conditions... foreach (Network.IXenConnection conn in ConnectionsManager.XenConnectionsCopy) conn.EndConnect(false); } private static void logApplicationStats() { Process p = Process.GetCurrentProcess(); log.Info("Log Application State"); log.InfoFormat("ExitCode: {0}", Environment.ExitCode); log.InfoFormat("Time since process started: {0}", (DateTime.Now - Process.GetCurrentProcess().StartTime).ToString()); log.InfoFormat("Handles open: {0}", p.HandleCount.ToString()); log.InfoFormat("USER handles open: {0}", Win32.GetGuiResourcesUserCount(p.Handle)); log.InfoFormat("GDI handles open: {0}", Win32.GetGuiResourcesGDICount(p.Handle)); log.InfoFormat("Thread count: {0}", p.Threads.Count); log.InfoFormat("Virtual memory size: {0}", Util.MemorySizeString(p.VirtualMemorySize64)); log.InfoFormat("Working set: {0}", Util.MemorySizeString(p.WorkingSet64)); log.InfoFormat("Private memory size: {0}", Util.MemorySizeString(p.PrivateMemorySize64)); log.InfoFormat("Nonpaged system memory size: {0}", Util.MemorySizeString(p.NonpagedSystemMemorySize64)); log.InfoFormat("Paged memory size: {0}", Util.MemorySizeString(p.PagedMemorySize64)); log.InfoFormat("Paged system memory size: {0}", Util.MemorySizeString(p.PagedSystemMemorySize64)); log.InfoFormat("Peak paged memory size: {0}", Util.MemorySizeString(p.PeakPagedMemorySize64)); log.InfoFormat("Peak virtual memory size: {0}", Util.MemorySizeString(p.PeakVirtualMemorySize64)); log.InfoFormat("Peak working set: {0}", Util.MemorySizeString(p.PeakWorkingSet64)); log.InfoFormat("Process priority class: {0}", p.PriorityClass.ToString()); log.InfoFormat("Privileged processor time: {0}", p.PrivilegedProcessorTime.ToString()); log.InfoFormat("Total processor time: {0}", p.TotalProcessorTime.ToString()); log.InfoFormat("User processor time: {0}", p.UserProcessorTime.ToString()); } static void Application_ThreadException(object sender, System.Threading.ThreadExceptionEventArgs e) { ProcessUnhandledException(sender, e.Exception, false); } static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) { ProcessUnhandledException(sender, (Exception)e.ExceptionObject, e.IsTerminating); } private static void ProcessUnhandledException(object sender, Exception e, bool isTerminating) { try { if (e != null) { log.Fatal("Uncaught exception", e); if (e.InnerException != null) log.Fatal("Inner exception", e.InnerException); } else { log.Fatal("Fatal error"); } logSystemDetails(); logApplicationStats(); if (!RunInAutomatedTestMode) { string filepath = GetLogFile(); ThreeButtonDialog d = new ThreeButtonDialog( new ThreeButtonDialog.Details( SystemIcons.Error, String.Format(Messages.MESSAGEBOX_PROGRAM_UNEXPECTED, HelpersGUI.DateTimeToString(DateTime.Now, "yyyy-MM-dd HH:mm:ss", false), filepath), Messages.MESSAGEBOX_PROGRAM_UNEXPECTED_TITLE)); // CA-44733 if (MainWindow != null && !IsExiting(MainWindow) && !MainWindow.InvokeRequired) d.ShowDialog(MainWindow); else d.ShowDialog(); } } catch (Exception exception) { try { log.Fatal("Fatal error while handling fatal error!", exception); } catch (Exception) { } if (!RunInAutomatedTestMode) { new ThreeButtonDialog(new ThreeButtonDialog.Details(SystemIcons.Error, exception.ToString(), Messages.XENCENTER)).ShowDialog(); // To be handled by WER throw; } } if (RunInAutomatedTestMode && TestExceptionString == null) TestExceptionString = e.GetBaseException().ToString(); } private static void SetDefaultFonts() { // Set the default font. // // Some locales have a known correct font, which we just use. // // Otherwise (e.g. English), we need to make sure that our fonts fit in with // our panel layout. This can be a problem on far-Eastern platforms in particular, because the default fonts there are wider in the // English range than Tahoma / Segoe UI. Anything bigger than them is going to cause us to overflow on the panels, so we force it back // to an appropriate font. // We need to be careful not to override the user's settings if they've deliberately scaled up the text for readability reasons, // so we only override if the font size is 9pt or smaller, which is the usual default. // // We also define a registry key to turn this off, just in case someone in the field complains. if (Registry.ForceSystemFonts) { log.Debug("ForceSystemFonts registry key is defined"); } else if (InvisibleMessages.LOCALE.StartsWith("ja-")) { Font new_font = (Environment.OSVersion.Version.Major < 6 || Environment.OSVersion.Version.Major == 6 && Environment.OSVersion.Version.Minor == 0) ? new Font("MS UI Gothic", 9.0f) : // 2K (5.0), XP (5.1), 2K3 and XP Pro (5.2), Vista and 2K8 (6.0) new Font("Meiryo UI", 9.0f); // Win 7 and 2K8R2 (6.1) and above log.DebugFormat("Running on localized resources with Japanese fonts; switching the font from {0} {1}pt to {2} {3}pt.", DefaultFont.Name, DefaultFont.Size, new_font.Name, new_font.Size); SetDefaultFonts(new_font); return; } else if (InvisibleMessages.LOCALE.StartsWith("zh-")) { Font new_font = Environment.OSVersion.Version.Major < 6 ? new Font("MS Shell Dlg", 9.0f) : // 2K (5.0), XP (5.1), 2K3 and XP Pro (5.2) new Font("Microsoft YaHei", 9.0f); // Vista and above log.DebugFormat("Running on localized resources with Simplified Chinese fonts; switching the font from {0} {1}pt to {2} {3}pt.", DefaultFont.Name, DefaultFont.Size, new_font.Name, new_font.Size); SetDefaultFonts(new_font); return; } else { if (DefaultFont.Size <= 9.0f) { Size s = Drawing.MeasureText("Lorum ipsum", DefaultFont); // Segoe UI 9pt gives 78x15 here. Tahoma 8pt gives 66x13. // We allow a bit of slop just in case antialias or hinting settings make a small difference. if (s.Width > 80 || s.Height > 16) { Font new_font = Environment.OSVersion.Version.Major < 6 ? new Font("Tahoma", 8.0f) : // 2K (5.0), XP (5.1), 2K3 and XP Pro (5.2) new Font("Segoe UI", 9.0f); // Vista and above log.DebugFormat("Running on default resources with large default font; switching the font from {0} {1}pt to {2} {3}pt.", DefaultFont.Name, DefaultFont.Size, new_font.Name, new_font.Size); SetDefaultFonts(new_font); return; } else { log.Debug("Running on default resources and happy with the font."); } } else { log.Debug("Running on default resources but the font is bigger than usual."); } } log.DebugFormat("Leaving the default font as {0} {1}pt.", DefaultFont.Name, DefaultFont.Size); SetDefaultFonts(DefaultFont); } private static void SetDefaultFonts(Font f) { DefaultFont = f; DefaultFontBold = new Font(f, FontStyle.Bold); DefaultFontUnderline = new Font(f, FontStyle.Underline); DefaultFontBoldUnderline = new Font(f, FontStyle.Bold | FontStyle.Underline); DefaultFontItalic = new Font(f, FontStyle.Italic); DefaultFontHeader = new Font(f.FontFamily, 9.75f, FontStyle.Bold); FormFontFixer.DefaultFont = f; } internal static void AssertOffEventThread() { if (Program.MainWindow.Visible && !Program.MainWindow.InvokeRequired) { FatalError(); } } internal static void AssertOnEventThread() { if (Program.MainWindow != null && Program.MainWindow.Visible && Program.MainWindow.InvokeRequired) { FatalError(); } } private static void FatalError() { string msg = String.Format(Messages.MESSAGEBOX_PROGRAM_UNEXPECTED, HelpersGUI.DateTimeToString(DateTime.Now, "yyyy-MM-dd HH:mm:ss", false), GetLogFile_()); log.Fatal(msg + "\n" + Environment.StackTrace); MainWindow m = Program.MainWindow; if (m == null) { log.Fatal("Program.MainWindow is null"); } else { log.FatalFormat("Program.MainWindow.Visible == {0}", m.Visible); log.FatalFormat("Program.MainWindow.InvokeRequired == {0}", m.InvokeRequired); log.FatalFormat("CurrentThread.Name == {0}", Thread.CurrentThread.Name); } if (RunInAutomatedTestMode) { if (TestExceptionString == null) TestExceptionString = msg + "\n" + Environment.StackTrace; } else { new ThreeButtonDialog(new ThreeButtonDialog.Details(SystemIcons.Error, msg, Messages.MESSAGEBOX_PROGRAM_UNEXPECTED_TITLE)).ShowDialog(); } } public static void ViewLogFiles() { string s = GetLogFile_(); if (s == null) { new ThreeButtonDialog(new ThreeButtonDialog.Details(SystemIcons.Error, Messages.MESSAGEBOX_LOGFILE_MISSING, Messages.XENCENTER)).ShowDialog(); } else { System.Diagnostics.Process.Start(Path.GetDirectoryName(s)); } } private static string GetLogFile() { string s = GetLogFile_(); return s == null ? "MISSING LOG FILE!" : s; } public static string GetLogFile_() { foreach (log4net.Appender.IAppender appender in log.Logger.Repository.GetAppenders()) { if (appender is log4net.Appender.FileAppender) { // Assume that the first FileAppender is the primary log file. return ((log4net.Appender.FileAppender)appender).File; } } return null; } private static readonly List HiddenObjects = new List(); internal static void HideObject(string opaqueRef) { lock (HiddenObjects) { HiddenObjects.Add(opaqueRef); } if (MainWindow != null) { MainWindow.RequestRefreshTreeView(); } } internal static void ShowObject(string opaqueRef) { lock (HiddenObjects) { HiddenObjects.Remove(opaqueRef); } if (MainWindow != null) { MainWindow.RequestRefreshTreeView(); } } internal static bool ObjectIsHidden(string opaqueRef) { lock (HiddenObjects) { return HiddenObjects.Contains(opaqueRef); } } /// /// Safely invoke the given delegate on the given control. /// public static void Invoke(Control c, MethodInvoker f) { try { if (!IsExiting(c)) { if (c.InvokeRequired) { MethodInvoker exceptionLogger = () => { try { f(); } catch (Exception e) { log.Error("Exception in Invoke", e); throw; } }; try { c.Invoke(exceptionLogger); } catch (Exception e) { log.Error(string.Format("Exception in Invoke (ControlType={0}, MethodName={1})", c.GetType(), f.Method.Name), e); throw; } } else { f(); } } } catch (ObjectDisposedException) { if (!IsExiting(c)) throw; } catch (InvalidAsynchronousStateException) { if (!IsExiting(c)) throw; } catch (InvalidOperationException) { if (!IsExiting(c)) throw; } } private static bool IsExiting(Control c) { return Exiting || c.Disposing || c.IsDisposed || !c.IsHandleCreated; } /// /// Safely invoke the given delegate on the given control. /// /// The result of the function call, or null if c is being disposed. public static object Invoke(Control c, Delegate f, params object[] p) { try { if (!IsExiting(c)) { if (c.InvokeRequired) { Func exceptionLogger = () => { try { return f.DynamicInvoke(p); } catch (Exception e) { log.Error("Exception in Invoke", e); throw; } }; return c.Invoke(exceptionLogger); } else { return f.DynamicInvoke(p); } } } catch (ObjectDisposedException) { if (!IsExiting(c)) throw; } catch (InvalidAsynchronousStateException) { if (!IsExiting(c)) throw; } catch (InvalidOperationException) { if (!IsExiting(c)) throw; } return null; } public static IAsyncResult BeginInvoke(Control c, Delegate f, params object[] p) { try { if (!IsExiting(c)) { return c.BeginInvoke(f, p); } } catch (ObjectDisposedException) { if (!IsExiting(c)) throw; } catch (InvalidAsynchronousStateException) { if (!IsExiting(c)) throw; } catch (InvalidOperationException) { if (!IsExiting(c)) throw; } return null; } public static void BeginInvoke(Control c, MethodInvoker f) { try { if (!IsExiting(c)) { c.BeginInvoke(f); } } catch (ObjectDisposedException) { if (!IsExiting(c)) throw; } catch (InvalidAsynchronousStateException) { if (!IsExiting(c)) throw; } catch (InvalidOperationException) { if (!IsExiting(c)) throw; } } public static string ClientVersion() { foreach (object o in Assembly.GetExecutingAssembly().GetCustomAttributes(true)) { if (o is XSVersionAttribute) { string result = ((XSVersionAttribute)o).Version; return result == "@PRODUCT_VERSION@" ? "PRIVATE" : result; } } return "MISSING"; } public static void ReconfigureConnectionSettings() { XenAPI.Session.Proxy = XenAdminConfigManager.Provider.GetProxyFromSettings(null); } private const string SplashWindowClass = "XenCenterSplash0001"; internal static void CloseSplash() { IntPtr hWnd = Win32.FindWindow(SplashWindowClass, null); if (hWnd == IntPtr.Zero) { //log.Warn("Couldn't find splash screen in CloseSplash()", new System.ComponentModel.Win32Exception()); return; } if (!Win32.PostMessage(hWnd, Win32.WM_DESTROY, IntPtr.Zero, IntPtr.Zero)) { log.Warn("PostMessage WM_DESTROY failed in CloseSplash()", new System.ComponentModel.Win32Exception()); } } public static void OpenURL(string url) { if (RunInAutomatedTestMode) return; try { System.Diagnostics.Process.Start(url); } catch (Exception e) { log.Debug(string.Format("Exception while opening url {0}:", url), e); } } /// /// If true action threads will close themselves instantly... /// public static bool ForcedExiting = false; public static Version Version { get { return Assembly.GetExecutingAssembly().GetName().Version; } } /// /// Gets the version in the form "a.b.c" /// public static string VersionThreePart { get { var version = Version; //The release version is obtained from branding.hg. From a //private build we get 0.0.0.0 so we should look out for //it and return it as is (or alternatively return 99.99.99) if (version.ToString() == "0.0.0.0") return "0.0.0.0"; return string.Format("{0}.{1}.{2}", version.Major, version.Minor, version.Build); } } public static string CurrentLanguage { get { return Thread.CurrentThread.CurrentCulture.TwoLetterISOLanguageName; } } public static string VersionAndLanguage { get { return string.Format("{0}.{1}", Version.ToString(), CurrentLanguage); } } public static CultureInfo CurrentCulture { get { return Thread.CurrentThread.CurrentCulture; } } } public enum ArgType { Import, License, Restore, Update, None, XenSearch, Passwords, Connect }; }