Fisheye: Tag 3f1c2bee78dbdca9102cd0153dedb1abf404a56c refers to a dead (removed) revision in file `Application/Riskeer/src/Application.Riskeer/App.cs'. Fisheye: No comparison available. Pass `N' to diff? Index: Application/Riskeer/src/Application.Riskeer/App.xaml.cs =================================================================== diff -u -rca6edcdc6d6bfb73dd817834bb2cc0d5149db92c -r3f1c2bee78dbdca9102cd0153dedb1abf404a56c --- Application/Riskeer/src/Application.Riskeer/App.xaml.cs (.../App.xaml.cs) (revision ca6edcdc6d6bfb73dd817834bb2cc0d5149db92c) +++ Application/Riskeer/src/Application.Riskeer/App.xaml.cs (.../App.xaml.cs) (revision 3f1c2bee78dbdca9102cd0153dedb1abf404a56c) @@ -19,19 +19,397 @@ // Stichting Deltares and remain full property of Stichting Deltares at all times. // All rights reserved. +using System; +using System.Collections.Generic; +using System.Configuration; +using System.Diagnostics; +using System.DirectoryServices.AccountManagement; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text.RegularExpressions; +using System.Threading; +using System.Windows; +using System.Windows.Controls.Primitives; +using System.Windows.Forms; +using Core.Common.Gui; +using Core.Common.Gui.Appenders; +using Core.Common.Gui.Forms.MainWindow; +using Core.Common.Gui.Helpers; +using Core.Common.Gui.Settings; +using Core.Common.Util; +using Core.Common.Util.Settings; +using Core.Plugins.Chart; +using Core.Plugins.CommonTools; +using Core.Plugins.Map; +using Core.Plugins.ProjectExplorer; +using log4net; +using log4net.Appender; +using Riskeer.ClosingStructures.Plugin; +using Riskeer.DuneErosion.Plugin; +using Riskeer.GrassCoverErosionInwards.Plugin; +using Riskeer.GrassCoverErosionOutwards.Plugin; +using Riskeer.HeightStructures.Plugin; +using Riskeer.Integration.Data; +using Riskeer.Integration.Forms; +using Riskeer.Integration.Plugin; +using Riskeer.MacroStabilityInwards.Plugin; +using Riskeer.Migration; +using Riskeer.Piping.Plugin; +using Riskeer.StabilityPointStructures.Plugin; +using Riskeer.StabilityStoneCover.Plugin; +using Riskeer.Storage.Core; +using Riskeer.WaveImpactAsphaltCover.Plugin; +using CoreCommonGuiResources = Core.Common.Gui.Properties.Resources; +using MessageBox = System.Windows.MessageBox; + namespace Application.Riskeer { /// /// Interaction logic for App.xaml. /// public partial class App { + // Start application after this process will exit (used during restart) + private const string argumentWaitForProcess = "--wait-for-process="; + + private const int numberOfDaysToKeepLogFiles = 30; + + private static int waitForProcessId = -1; + private static Mutex singleInstanceMutex; + private static string fileToOpen = string.Empty; + + private readonly ILog log; + private GuiCore gui; + + private delegate void ExceptionDelegate(Exception exception); + /// /// Creates a new instance of . /// public App() { - Initialize(); + LogConfigurator.Initialize(); + + log = LogManager.GetLogger(typeof(App)); + + SettingsHelper.Instance = new RiskeerSettingsHelper(); + SetLanguage(); + + string userDisplay = UserDisplay(); + log.Info(string.Format(CoreCommonGuiResources.App_Starting_Riskeer_version_0_by_user_0, + SettingsHelper.Instance.ApplicationVersion, + userDisplay)); } + + protected override void OnExit(ExitEventArgs e) + { + singleInstanceMutex?.ReleaseMutex(); + base.OnExit(e); + } + + private void OnStartup(object sender, StartupEventArgs e) + { + ParseArguments(e.Args); + + WaitForPreviousInstanceToExit(); + if (IsNotFirstInstance()) + { + MessageBox.Show(CoreCommonGuiResources.App_ShutdownIfNotFirstInstance_Cannot_start_multiple_instances_of_Riskeer_Please_close_the_other_instance_first); + Shutdown(1); + return; + } + + DeleteOldLogFiles(); + + Resources.Add(SystemParameters.MenuPopupAnimationKey, PopupAnimation.None); + + var settings = new GuiCoreSettings + { + SupportEmailAddress = "www.helpdeskwater.nl", + SupportPhoneNumber = "+31 (0)88-797 7102", + MainWindowTitle = "Riskeer", + ManualFilePath = "..\\Gebruikershandleiding Riskeer 19.1.1.pdf" + }; + var mainWindow = new MainWindow(); + var projectMigrator = new ProjectMigrator(new DialogBasedInquiryHelper(mainWindow)); + gui = new GuiCore(mainWindow, new StorageSqLite(), projectMigrator, new RiskeerProjectFactory(), settings) + { + Plugins = + { + new ProjectExplorerPlugin(), + new CommonToolsPlugin(), + new RiskeerPlugin(), + new ClosingStructuresPlugin(), + new StabilityPointStructuresPlugin(), + new WaveImpactAsphaltCoverPlugin(), + new GrassCoverErosionInwardsPlugin(), + new GrassCoverErosionOutwardsPlugin(), + new PipingPlugin(), + new HeightStructuresPlugin(), + new StabilityStoneCoverPlugin(), + new DuneErosionPlugin(), + new MacroStabilityInwardsPlugin(), + new ChartPlugin(), + new MapPlugin() + } + }; + + RunRiskeer(); + + mainWindow.Show(); + } + + private void RunRiskeer() + { + string loaderDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + if (loaderDirectory != null) + { + Environment.CurrentDirectory = loaderDirectory; + } + + System.Windows.Forms.Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException, true); + + // handle exception from UI thread + System.Windows.Forms.Application.ThreadException += Application_ThreadException; + + // handle exception from all threads except UI + AppDomain.CurrentDomain.UnhandledException += AppDomain_UnhandledException; + + gui.Run(fileToOpen); + + // Riskeer started, clean-up all possible memory + GC.Collect(); + GC.WaitForPendingFinalizers(); + } + + private void AppDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) + { + Exception exception = e.ExceptionObject as Exception ?? new Exception(CoreCommonGuiResources.App_Unhandled_exception); + + HandleExceptionOnMainThread(exception); + } + + private void Application_ThreadException(object sender, ThreadExceptionEventArgs e) + { + HandleExceptionOnMainThread(e.Exception); + } + + private void HandleExceptionOnMainThread(Exception exception) + { + var control = (Control) gui.MainWindow.PropertyGrid; + + if (control != null && control.InvokeRequired) + { + // Invoke executes a delegate on the thread that owns _MainForms's underlying window handle. + control.Invoke(new ExceptionDelegate(HandleException), exception); + } + else + { + HandleException(exception); + } + } + + private void HandleException(Exception exception) + { + log.Error(CoreCommonGuiResources.App_Unhandled_exception, exception); + + if (gui?.MainWindow != null) + { + using (var exceptionDialog = new ExceptionDialog(gui.MainWindow, gui, exception) + { + OpenLogClicked = () => gui.ApplicationCommands?.OpenLogFileExternal() + }) + { + if (exceptionDialog.ShowDialog() == DialogResult.OK) + { + Restart(); + + return; + } + } + } + + Environment.Exit(1); + } + + private static void Restart() + { + Process.Start(typeof(App).Assembly.Location, argumentWaitForProcess + Process.GetCurrentProcess().Id); + Environment.Exit(1); + } + + /// + /// app.config has been configured to use + /// to write log files to the Riskeer user data folder. This method deletes the old log files + /// that have been written there. + /// + private static void DeleteOldLogFiles() + { + try + { + IOUtils.DeleteOldFiles(GetLogFileDirectory(), "*.log", numberOfDaysToKeepLogFiles); + } + catch (Exception e) + { + if (e is ArgumentException || e is IOException) + { + return; + } + + throw; + } + } + + private static bool IsNotFirstInstance() + { + var hasMutex = false; + + try + { + if (!AcquireSingleInstancePerUserMutex()) + { + return true; + } + + hasMutex = true; + } + finally + { + if (!hasMutex) + { + singleInstanceMutex = null; + } + } + + return false; + } + + private static bool AcquireSingleInstancePerUserMutex() + { + // Include the user name in the (global) mutex to ensure we limit only the number of instances per + // user, not per system (essential on for example Citrix systems). + singleInstanceMutex = new Mutex(true, $"Riskeer-single-instance-mutex-{Environment.UserName}", out bool createdNew); + + return createdNew; + } + + /// + /// If variable waitForProcessId > -1, the application will hold until + /// the process with that ID has exited. + /// + private static void WaitForPreviousInstanceToExit() + { + // Wait until previous version of Riskeer has exited + if (waitForProcessId == -1) + { + return; + } + + try + { + Process process = Process.GetProcessById(waitForProcessId); + process.WaitForExit(); + } + catch + { + //Ignored, because the process may already be closed + } + } + + private static bool ParseFileArgument(string potentialPath) + { + if (potentialPath.Length > 0) + { + try + { + IOUtils.ValidateFilePath(potentialPath); + fileToOpen = potentialPath; + return true; + } + catch (ArgumentException) + { + return false; + } + } + + return false; + } + + /// + /// Parses the process' start-up parameters. + /// + /// List of start-up parameters. + private static void ParseArguments(IEnumerable arguments) + { + var argumentWaitForProcessRegex = new Regex("^" + argumentWaitForProcess + @"(?\d+)$", RegexOptions.IgnoreCase); + foreach (string arg in arguments) + { + Match match = argumentWaitForProcessRegex.Match(arg); + if (match.Success) + { + int pid = int.Parse(match.Groups["processId"].Value); + if (pid > 0) + { + waitForProcessId = pid; + break; + } + } + + if (ParseFileArgument(arg)) + { + break; + } + } + } + + private static void SetLanguage() + { + string language = ConfigurationManager.AppSettings["language"]; + if (language != null) + { + var localMachineDateTimeFormat = (DateTimeFormatInfo) Thread.CurrentThread.CurrentCulture.DateTimeFormat.Clone(); + localMachineDateTimeFormat.DayNames = CultureInfo.InvariantCulture.DateTimeFormat.DayNames; + localMachineDateTimeFormat.MonthNames = CultureInfo.InvariantCulture.DateTimeFormat.MonthNames; + localMachineDateTimeFormat.AbbreviatedDayNames = CultureInfo.InvariantCulture.DateTimeFormat.AbbreviatedDayNames; + localMachineDateTimeFormat.AbbreviatedMonthGenitiveNames = CultureInfo.InvariantCulture.DateTimeFormat.AbbreviatedMonthGenitiveNames; + localMachineDateTimeFormat.AbbreviatedMonthNames = CultureInfo.InvariantCulture.DateTimeFormat.AbbreviatedMonthNames; + + var cultureInfo = new CultureInfo(language) + { + NumberFormat = Thread.CurrentThread.CurrentCulture.NumberFormat, + DateTimeFormat = localMachineDateTimeFormat + }; + + Thread.CurrentThread.CurrentUICulture = cultureInfo; + Thread.CurrentThread.CurrentCulture = cultureInfo; + } + } + + private static string UserDisplay() + { + try + { + return $"{UserPrincipal.Current.DisplayName} ({UserPrincipal.Current.SamAccountName})"; + } + catch (SystemException) + { + // Cannot only catch specified exceptions, as there are some hidden exception + // that can be thrown when calling UserPrincipal.Current. + return Environment.UserName; + } + } + + private static string GetLogFileDirectory() + { + FileAppender fileAppender = LogManager.GetAllRepositories() + .SelectMany(r => r.GetAppenders()) + .OfType() + .FirstOrDefault(); + return string.IsNullOrWhiteSpace(fileAppender?.File) + ? string.Empty + : Path.GetDirectoryName(fileAppender.File); + } } } \ No newline at end of file