Index: Application/Riskeer/src/Application.Riskeer/App.cs =================================================================== diff -u --- Application/Riskeer/src/Application.Riskeer/App.cs (revision 0) +++ Application/Riskeer/src/Application.Riskeer/App.cs (revision 76cdcb41e7687ffde95a1eaf235c000f48b707c7) @@ -0,0 +1,421 @@ +// Copyright (C) Stichting Deltares 2019. All rights reserved. +// +// This file is part of Riskeer. +// +// Riskeer is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// All names, logos, and references to "Deltares" are registered trademarks of +// 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; +#if DEVELOPMENT +using Demo.Riskeer.GUIs; + +#endif + +namespace Application.Riskeer +{ + // Partial class introduced for avoiding problems in relation to dynamically resolving assemblies + // (SetupAssemblyResolver must be called before any dependencies are needed). + 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 string fileToOpen = string.Empty; + + private static Mutex singleInstanceMutex; + + private static int waitForProcessId = -1; + + private ILog log; + + private GuiCore gui; + + private delegate void ExceptionDelegate(Exception exception); + + protected override void OnExit(ExitEventArgs e) + { + singleInstanceMutex?.ReleaseMutex(); + base.OnExit(e); + } + + private void Initialize() + { + Logger.Setup(); + 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)); + } + + private void OnStartup(object sender, StartupEventArgs e) + { + ParseArguments(e.Args); + + Resources.Add(SystemParameters.MenuPopupAnimationKey, PopupAnimation.None); + + WaitForPreviousInstanceToExit(); + if (ShutdownIfNotFirstInstance()) + { + return; + } + + DeleteOldLogFiles(); + + 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() +#if DEVELOPMENT + , + new DemoPlugin() +#endif + } + }; + + 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 void DeleteOldLogFiles() + { + try + { + IOUtils.DeleteOldFiles(GetLogFileDirectory(), "*.log", numberOfDaysToKeepLogFiles); + } + catch (Exception e) + { + if (e is ArgumentException || e is IOException) + { + return; + } + + throw; + } + } + + private bool ShutdownIfNotFirstInstance() + { + var hasMutex = false; + + try + { + if (!AcquireSingleInstancePerUserMutex()) + { + MessageBox.Show(CoreCommonGuiResources.App_ShutdownIfNotFirstInstance_Cannot_start_multiple_instances_of_Riskeer_Please_close_the_other_instance_first); + Shutdown(1); + return true; //done here + } + + 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 Index: Application/Riskeer/src/Application.Riskeer/App.xaml =================================================================== diff -u -rf9ad6aa27e2c71c187325e65c98d0f4ae6873e0d -r76cdcb41e7687ffde95a1eaf235c000f48b707c7 --- Application/Riskeer/src/Application.Riskeer/App.xaml (.../App.xaml) (revision f9ad6aa27e2c71c187325e65c98d0f4ae6873e0d) +++ Application/Riskeer/src/Application.Riskeer/App.xaml (.../App.xaml) (revision 76cdcb41e7687ffde95a1eaf235c000f48b707c7) @@ -23,7 +23,7 @@ + Startup="OnStartup" ShutdownMode="OnLastWindowClose"> Index: Application/Riskeer/src/Application.Riskeer/App.xaml.cs =================================================================== diff -u -r284632cdf71f22c0008c9a140e483f9f3b7af5e8 -r76cdcb41e7687ffde95a1eaf235c000f48b707c7 --- Application/Riskeer/src/Application.Riskeer/App.xaml.cs (.../App.xaml.cs) (revision 284632cdf71f22c0008c9a140e483f9f3b7af5e8) +++ Application/Riskeer/src/Application.Riskeer/App.xaml.cs (.../App.xaml.cs) (revision 76cdcb41e7687ffde95a1eaf235c000f48b707c7) @@ -20,15 +20,10 @@ // All rights reserved. using System; -using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; -using System.Text.RegularExpressions; -using System.Windows; -using System.Windows.Controls.Primitives; using Core.Common.Assembly; -using Core.Common.Util; namespace Application.Riskeer { @@ -37,35 +32,15 @@ /// public partial class App { - // Start application after this process will exit (used during restart) - private const string argumentWaitForProcess = "--wait-for-process="; - - private static string fileToOpen = string.Empty; - - private static RiskeerRunner runner; - + /// + /// Creates a new instance of . + /// public App() { SetupAssemblyResolver(); - - runner = new RiskeerRunner(); + Initialize(); } - protected override void OnExit(ExitEventArgs e) - { - runner.OnExit(); - base.OnExit(e); - } - - private void App_Startup(object sender, StartupEventArgs e) - { - ParseArguments(e.Args); - - Resources.Add(SystemParameters.MenuPopupAnimationKey, PopupAnimation.None); - - runner.Run(fileToOpen, this); - } - private static void SetupAssemblyResolver() { string assemblyDirectory = Path.Combine(GetApplicationDirectory(), "Built-in", "Managed"); @@ -90,52 +65,6 @@ } } - 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) - { - RiskeerRunner.WaitForProcessId = pid; - break; - } - } - - if (ParseFileArgument(arg)) - { - break; - } - } - } - private static string GetApplicationDirectory() { DirectoryInfo executingAssemblyDirectoryInfo = Directory.GetParent(Assembly.GetExecutingAssembly().Location); Fisheye: Tag 76cdcb41e7687ffde95a1eaf235c000f48b707c7 refers to a dead (removed) revision in file `Application/Riskeer/src/Application.Riskeer/RiskeerRunner.cs'. Fisheye: No comparison available. Pass `N' to diff?