using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Threading.Tasks; using System.Xml.Linq; using Barotrauma.LuaCs.Data; using FluentResults; using Microsoft.Toolkit.Diagnostics; using OneOf; namespace Barotrauma.LuaCs; public sealed class SettingsFileParserService : IParserServiceOneToManyAsync, IParserServiceOneToManyAsync { #region DisposalControl private AsyncReaderWriterLock _operationLock = new(); public void Dispose() { using var lck = _operationLock.AcquireWriterLock().ConfigureAwait(false).GetAwaiter().GetResult(); if (!ModUtils.Threading.CheckIfClearAndSetBool(ref _isDisposed)) { return; } _storageService.Dispose(); _storageService = null; } private int _isDisposed = 0; public bool IsDisposed { get => ModUtils.Threading.GetBool(ref _isDisposed); private set => ModUtils.Threading.SetBool(ref _isDisposed, value); } #endregion private IStorageService _storageService; public SettingsFileParserService(IStorageService storageService) { _storageService = storageService; } async Task>> IParserServiceOneToManyAsync .TryParseResourcesAsync(IConfigResourceInfo src) { Guard.IsNotNull(src, nameof(src)); Guard.IsNotNull(src.OwnerPackage, nameof(src.OwnerPackage)); using var lck = await _operationLock.AcquireReaderLock(); IService.CheckDisposed(this); if (src.FilePaths.IsDefaultOrEmpty) { return ReturnFail($"The config file list is empty."); } var parsedInfo = ImmutableArray.CreateBuilder(); foreach ((ContentPath path, Result docLoadResult) res in await _storageService.LoadPackageXmlFilesAsync(src.FilePaths)) { if (res.docLoadResult.IsFailed) { return ReturnFail($"Failed to load document for {src.OwnerPackage.Name}").WithErrors(res.docLoadResult.Errors); } var settingElements = res.docLoadResult.Value.GetChildElement("Configuration") .GetChildElements("Settings").SelectMany(e => e.GetChildElements("Setting")).ToImmutableArray(); if (settingElements.IsDefaultOrEmpty) { continue; } var packageIdent = res.path.ContentPackage.ToIdentifier().ToString(); foreach (var element in settingElements) { var name = element.GetAttributeString("Name", string.Empty); if (name.IsNullOrWhiteSpace()) { return ReturnFail( $"The internal name for a setting in the config file '{res.path.FullPath}' is empty!"); } var newSetting = new ConfigInfo() { InternalName = name, OwnerPackage = res.path.ContentPackage, DataType = element.GetAttributeString("Type", string.Empty), Element = element, EditableStates = element.GetAttributeBool("ReadOnly", false) ? RunState.Unloaded : element.GetAttributeBool("AllowChangesWhileExecuting", true) ? RunState.Running : RunState.LoadedNoExec, NetSync = element.GetAttributeEnum("NetSync", NetSync.None), #if CLIENT DisplayName = $"{packageIdent}.{name}.DisplayName", Description = $"{packageIdent}.{name}.Description", DisplayCategory = $"{packageIdent}.{name}.DisplayCategory", ShowInMenus = element.GetAttributeBool("ShowInMenus", true), Tooltip = $"{packageIdent}.{name}.Tooltip", ImageIconPath = element.GetAttributeString("ImageIcon", string.Empty) is {} val && !val.IsNullOrWhiteSpace() ? ContentPath.FromRaw(res.path.ContentPackage, val) : ContentPath.Empty #endif }; if (!IsInfoValid(newSetting)) { return ReturnFail($"A setting was invalid. ContentPackage: {res.path.ContentPackage}"); } parsedInfo.Add(newSetting); } } return FluentResults.Result.Ok(parsedInfo.ToImmutable()); // Helpers FluentResults.Result ReturnFail(string msg) { return FluentResults.Result.Fail($"{nameof(IParserServiceOneToManyAsync.TryParseResourcesAsync)}: {msg}"); } bool IsInfoValid(ConfigInfo info) { return info.OwnerPackage != null && !info.InternalName.IsNullOrWhiteSpace() && !info.DataType.IsNullOrWhiteSpace() && !info.DataType.IsNullOrWhiteSpace() && info.Element != null #if CLIENT && !info.DisplayName.IsNullOrWhiteSpace() && !info.Description.IsNullOrWhiteSpace() && !info.DisplayCategory.IsNullOrWhiteSpace() && !info.Tooltip.IsNullOrWhiteSpace() #endif ; } } async Task>> IParserServiceOneToManyAsync .TryParseResourcesAsync(IConfigResourceInfo src) { Guard.IsNotNull(src, nameof(src)); Guard.IsNotNull(src.OwnerPackage, nameof(src.OwnerPackage)); using var lck = await _operationLock.AcquireReaderLock(); IService.CheckDisposed(this); if (src.FilePaths.IsDefaultOrEmpty) { return ReturnFail($"The config file list is empty."); } var parsedInfo = ImmutableArray.CreateBuilder(); foreach ((ContentPath path, Result docLoadResult) res in await _storageService .LoadPackageXmlFilesAsync(src.FilePaths)) { if (res.docLoadResult.IsFailed) { return ReturnFail($"Failed to load document for {src.OwnerPackage.Name}") .WithErrors(res.docLoadResult.Errors); } var profileCollection = res.docLoadResult.Value.GetChildElement("Configuration") .GetChildElement("Profiles"); if (profileCollection == null) { continue; } foreach (var profile in profileCollection.GetChildElements("Profile")) { var profileName = profile.GetAttributeString("Name", string.Empty); Guard.IsNotNullOrWhiteSpace(profileName, nameof(profileName)); var settingValues = profile.GetChildElements("SettingValue").ToImmutableArray(); if (settingValues.IsDefaultOrEmpty) { ThrowHelper.ThrowArgumentNullException(nameof(settingValues)); } var profileValuesBuilder = ImmutableArray.CreateBuilder<(string ConfigName, XElement Value)>(); foreach (var settingValue in settingValues) { var cfgName = settingValue.GetAttributeString("Name", string.Empty); Guard.IsNotNullOrWhiteSpace(cfgName, nameof(cfgName)); profileValuesBuilder.Add((cfgName, settingValue)); } parsedInfo.Add(new ConfigProfileInfo() { InternalName = profileName, OwnerPackage = res.path.ContentPackage, ProfileValues = profileValuesBuilder.ToImmutable() }); } } return parsedInfo.ToImmutable(); FluentResults.Result ReturnFail(string msg) { return FluentResults.Result.Fail($"{nameof(IParserServiceOneToManyAsync.TryParseResourcesAsync)}: {msg}"); } } }