// Copyright (C) Stichting Deltares 2025. All rights reserved. // // This file is part of the application DAM - UI. // // DAM - UI 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.IO; using System.Linq; using Deltares.Dam.Data.DataPlugins.Configuration; using Deltares.Dam.Data.IO; using Deltares.Standard; using Deltares.Standard.EventPublisher; using Deltares.Standard.IO; using Deltares.Standard.IO.DtoAssembler; using Deltares.Standard.IO.Xml; using Deltares.Standard.Language; using Deltares.Standard.Logging; using Deltares.Standard.Reflection; using ProgressDelegate = Deltares.DamEngine.Data.Standard.Calculation.ProgressDelegate; namespace Deltares.Dam.Data; public class DamProjectException : Exception { public DamProjectException(string message) : base(message) {} } public class DamProject : IDisposable { public static ProjectPathLocation ProjectWorkingPathLocation = ProjectPathLocation.InApplicationMap; private static string testProgramVersion; public static string UserWorkingPath = GetNewTempDirectory(); public static string ProjectMapWorkingPath = GetNewTempDirectory(); private const string CalculationLogFileExtension = "calclog.txt"; private string projectFileName; private DamProjectData damProjectData; public DamProject() { damProjectData = new DamProjectData(); } //[XmlIgnore] public static string ProjectMap { get; set; } //[XmlIgnore] public static string CalculationMap { get; set; } public string ProjectFileName { get => projectFileName; set { DataEventPublisher.BeforeChange(this, "ProjectFileName"); projectFileName = value; ProjectMap = Path.GetDirectoryName(Path.GetFullPath(value)); CalculationMap = Path.GetFileName(Path.ChangeExtension(value, "Calc")); ProjectMapWorkingPath = Path.Combine(Path.GetDirectoryName(Path.GetFullPath(value)), CalculationMap); //damProjectFolder = Path.GetDirectoryName(value); DataEventPublisher.AfterChange(this, "ProjectFileName"); } } //[XmlIgnore] public DamProjectData DamProjectData { get { return damProjectData; } set { string propertyName = this.GetMemberName(x => x.DamProjectData); DataEventPublisher.BeforeChange(this, propertyName); damProjectData = value; DataEventPublisher.AfterChange(this, propertyName); } } public static string ProjectWorkingPath { get { switch (ProjectWorkingPathLocation) { case ProjectPathLocation.InUserMap: return UserWorkingPath; case ProjectPathLocation.InProjectMap: return ProjectMapWorkingPath; default: return Path.GetDirectoryName(typeof(DamProject).Assembly.Location) + @"\DamCalculation"; } } } public static string GetNewTempDirectory() { var counter = 1; while (true) { string dir = Path.GetTempPath() + Path.DirectorySeparatorChar + "DAM" + counter; if (!Directory.Exists(dir) && !File.Exists(dir)) { return dir; } counter++; } } /// /// Imports all the data /// /// The data folder. /// The data source container. /// Type of the dam project. /// The progress. /// public void Import(string dataFolder, DataSourceContainer dataSourceContainer, DamProjectType damProjectType, ProgressDelegate progress) { string damProjectFolder = Path.GetDirectoryName(projectFileName); DataSourceManager.DataSources = DamProjectData.DataSources; List logMessages; // import Dike dike = DikeImporter.ImportDataForDike( damProjectFolder, dataFolder, dataSourceContainer, damProjectType, progress, out logMessages); // create data DamProjectData = new DamProjectData { Dike = dike, DataSourceEsriProjection = dataSourceContainer.DataSourceEsriProjection }; // handle logs if (logMessages.Any()) { LogManager.Messages.Clear(); LogManager.Messages.AddRange(logMessages); } // Add stability as standard mechanism when not yet available. if (damProjectData.DamProjectCalculationSpecification.DamCalculationSpecifications.Count == 0) { damProjectData.DamProjectCalculationSpecification.DamCalculationSpecifications.Add(new DamFailureMechanismeCalculationSpecification()); } } /// /// Loads DAM project data from the given .damx file. Used by DAMLive, so do not remove. /// /// Name of the file. /// The project data /// /// public static DamProjectData LoadData(string fileName) { if (string.IsNullOrWhiteSpace(fileName)) { throw new ArgumentException("fileName"); } if (!File.Exists(fileName)) { throw new FileNotFoundException(fileName); } if (!fileName.EndsWith("damx")) { throw new ArgumentException("No damx file", "fileName"); } using (var damProject = new DamProject()) { return damProject.OpenXMLProject(fileName) as DamProjectData; } } /// /// Saves DAM project data to the given .damx file. /// /// If the file already exists it will be overriden /// Name of the file. /// The DAM project data to be saved. public static void SaveData(string fileName, DamProjectData data) { if (string.IsNullOrWhiteSpace(fileName)) { throw new ArgumentException("fileName"); } var project = new DamProject { DamProjectData = data }; project.SaveXMLProject(fileName, null); } /// /// Sets the test program version. /// Note: To be used by unit tests only. /// /// The test version. public static void SetTestProgramVersion(string testVersion) { testProgramVersion = testVersion; } /// /// Opens the XML project. /// /// Name of the file. /// public object OpenXMLProject(string fileName) { // To retrieve file version from xml, add handler. var damProjectVersionXmlHandler = new DamProjectVersionXmlHandler(); XmlHandler.RegisterObjectHandler(damProjectVersionXmlHandler); ClearProject(); ProjectFileName = fileName; DataEventPublisher.InvokeWithoutPublishingEvents(() => { var xmlSerializer = new XmlDeserializer(); object project = xmlSerializer.XmlDeserialize(fileName, typeof(DamProjectData), new DefaultClassFactory()); damProjectData = (DamProjectData) project; if (damProjectData != null) { damProjectData.VersionInfo.InitVersionInfoAfterRead(); if (damProjectData.VersionInfo.FileVersionAsRead < damProjectData.VersionInfo.FileVersion) { throw new DamProjectException(LocalizationManager.GetTranslatedText(GetType(), "CanNotReadOldFile")); } string damProjectVersion = damProjectVersionXmlHandler.GetDamProjectVersion(); damProjectVersion = GetMainVersion(damProjectVersion); if (damProjectVersion != null) { string currentProgramVersion = About.Version; if (currentProgramVersion == null) { currentProgramVersion = testProgramVersion; } currentProgramVersion = GetMainVersion(currentProgramVersion); if (!currentProgramVersion.Equals(damProjectVersion)) { damProjectData.ClearResults(); } } } }); // Add stability as standard mechanism when not yet available. if (!damProjectData.DamProjectCalculationSpecification.DamCalculationSpecifications.Any()) { damProjectData.DamProjectCalculationSpecification.DamCalculationSpecifications.Add(new DamFailureMechanismeCalculationSpecification()); } if (damProjectData != null) { DataEventPublisher.AfterChange(damProjectData, "MaxCalculationCores"); } return damProjectData; } /// /// Assigns the specified 2D-geometry mapname for all dikes where it is not assigned. /// /// Name of the map. public void AssignGeometry2DMapnameIfNotAssigned(string mapName) { if (damProjectData.Dike != null && String.IsNullOrEmpty(damProjectData.Dike.MapForSoilGeometries2D)) { damProjectData.Dike.MapForSoilGeometries2D = mapName; foreach (Location location in damProjectData.Dike.Locations) { location.MapForSoilGeometries2D = mapName; } } } /// /// Create a destination filename for new location of soil materials database /// /// /// public static string CreateNewGeometry2DMapname(string projectFilename) { string destMapname = Path.GetFileNameWithoutExtension(projectFilename) + ".geometries2D" + @"\"; return destMapname; } /// /// Save the project in XML format /// /// /// public void SaveXMLProject(string fileName, object project) { // Try to retrieve the data source folder from this object. string dataSourceFolder = null; if (DamProjectData != null) { string damDataSourceFilePath = DamProjectData.DamDataSourceFileName; dataSourceFolder = string.IsNullOrEmpty(damDataSourceFilePath) ? damDataSourceFilePath : Path.GetDirectoryName(damDataSourceFilePath); } // If the data source is not available, try to retrieve it from // the argument. The data source folder must be set in order to // create the geometries 2D.0 folder var damProject = project as DamProject; if (damProject != null && dataSourceFolder == null) { dataSourceFolder = Path.GetDirectoryName(damProject.DamProjectData.DamDataSourceFileName); } fileName = Path.ChangeExtension(fileName, "damx"); string savedProjectMap = ProjectMap; ProjectFileName = fileName; if (savedProjectMap == null) { savedProjectMap = ProjectMap; } damProjectData.VersionInfo.InitVersionInfo(); DataEventPublisher.InvokeWithoutPublishingEvents(() => { EnsureGeometries2DMapWithProject(fileName, savedProjectMap, dataSourceFolder); var xmlSerializer = new XmlSerializer(); xmlSerializer.Serialize(damProjectData, fileName); }); } /// /// Saves the calculation log to text file. /// /// The calculation messages. /// Name of the file. public void SaveCalculationLogToTextFile(List calculationMessages, string fileName) { if (calculationMessages != null && calculationMessages.Count > 0) { var messages = new List(); foreach (LogMessage calculationMessage in calculationMessages) { messages.Add(calculationMessage.MessageType + ": " + calculationMessage.Message + ", " + calculationMessage.Detail + ", " + calculationMessage.SubjectName); } fileName = Path.ChangeExtension(fileName, DateTime.Now.ToFileTime() + CalculationLogFileExtension); File.WriteAllLines(fileName, messages); } } public void DeleteResults() { DeleteResultData(); RemoveCalculationFolder(); } public void ClearProject() { // DeleteResultData(); damProjectData = null; CalculationMap = ""; ProjectMap = ""; damProjectData = new DamProjectData(); LogManager.Clear(); } public void Dispose() { DamProjectData.Dispose(); } /// /// Gets the main version (first two numbers of full version). /// /// The full version. /// private string GetMainVersion(string fullVersion) { if (fullVersion.Contains(".")) { string[] currentVer = fullVersion.Split(".".ToCharArray()); string main = currentVer[0]; string sec = currentVer[1]; return main + "." + sec; } return fullVersion; } /// /// Copies the 2D-geometries. /// /// The original mapname. /// The destination mapname. private void CopyGeometries(string orgMapname, string destMapname) { if (Directory.Exists(orgMapname)) { if (!Directory.Exists(destMapname)) { Directory.CreateDirectory(destMapname); } foreach (string file in Directory.GetFiles(orgMapname)) { string fileName = Path.GetFileName(file); if (fileName != null) { string destFile = Path.Combine(destMapname, fileName); if (File.Exists(destFile)) { File.Delete(destFile); } File.Copy(file, destFile); } } } } /// /// Make sure the map with 2d_geometries is copied besides the project file /// and reference to the map is set to it /// /// the filename of the project /// The org project map in case of file save as. /// The directory in which the .defx file resides. private void EnsureGeometries2DMapWithProject(string fileName, string projectDirectory, string dataSourceDirectory) { if (!String.IsNullOrEmpty(damProjectData.Dike.MapForSoilGeometries2D)) { string mapForSoilGeometries = damProjectData.Dike.MapForSoilGeometries2D; string sourceMapName = DetermineGeometriesSource(mapForSoilGeometries, projectDirectory, dataSourceDirectory); // Only copy files if map of geometries is assigned and exists if (!String.IsNullOrEmpty(sourceMapName) && Directory.Exists(sourceMapName)) { string destMapname = CreateNewGeometry2DMapname(fileName); string fullFilename = Path.GetFullPath(fileName); string destFullMapname = Path.Combine(Path.GetDirectoryName(fullFilename), destMapname); string orgFullMapname = Path.GetFullPath(sourceMapName); if (!orgFullMapname.Equals(destFullMapname)) { CopyGeometries(orgFullMapname, destFullMapname); } // Store the relative path to the 2d-geometries damProjectData.Dike.MapForSoilGeometries2D = destMapname; foreach (Location location in damProjectData.Dike.Locations) { location.MapForSoilGeometries2D = destMapname; } } } } /// /// Determines from which folder the soil geometry definitions should be retrieved from. /// /// The directory of the soil geometries as specified by the .defx file /// The directory of the .damx project file. /// The directory of the .defx file. /// The directory of where the soil geometries reside. private static string DetermineGeometriesSource(string soilGeometryDirectory, string projectDirectory, string dataSourceDirectory) { if (Directory.Exists(soilGeometryDirectory)) { return soilGeometryDirectory; } string directory = Path.Combine(projectDirectory, soilGeometryDirectory); return Directory.Exists(directory) ? directory : Path.Combine(dataSourceDirectory, soilGeometryDirectory); } private void DeleteResultData() { damProjectData.DeleteResults(); } private static void RemoveCalculationFolder() { if (!Directory.Exists(ProjectWorkingPath)) { return; } try { Directory.Delete(ProjectWorkingPath, true); } catch (IOException e) { throw new DamProjectException(e.Message); } } }