[Save/Sync] In-Progress ModConfigXml loading rewrite.
+ Fixed async operations lock for Dispose() pattern in working files. + Rewrote StorageService.cs: --- Now uses ContentPath instead of raw strings where possible. --- Now throws exceptions for developer errors and critical program states. + Rewrote ModConfigService.cs: --- All functions are now completely async. + Removed ConfigProfilesResources completely as they exist in common Config xml files. + Somewhat simplified package data and processes.
This commit is contained in:
@@ -20,7 +20,6 @@ public record ModConfigInfo : IModConfigInfo
|
||||
public ImmutableArray<IAssemblyResourceInfo> Assemblies { get; init; }
|
||||
public ImmutableArray<ILuaScriptResourceInfo> LuaScripts { get; init; }
|
||||
public ImmutableArray<IConfigResourceInfo> Configs { get; init; }
|
||||
public ImmutableArray<IConfigProfileResourceInfo> ConfigProfiles { get; init; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -30,7 +29,6 @@ public record ModConfigInfo : IModConfigInfo
|
||||
public record AssemblyResourcesInfo(ImmutableArray<IAssemblyResourceInfo> Assemblies) : IAssembliesResourcesInfo;
|
||||
public record LuaScriptsResourcesInfo(ImmutableArray<ILuaScriptResourceInfo> LuaScripts) : ILuaScriptsResourcesInfo;
|
||||
public record ConfigResourcesInfo(ImmutableArray<IConfigResourceInfo> Configs) : IConfigsResourcesInfo;
|
||||
public record ConfigProfilesResourcesInfo(ImmutableArray<IConfigProfileResourceInfo> ConfigProfiles) : IConfigProfilesResourcesInfo;
|
||||
|
||||
public record BaseResourceInfo : IBaseResourceInfo
|
||||
{
|
||||
@@ -51,10 +49,11 @@ public record AssemblyResourceInfo : BaseResourceInfo, IAssemblyResourceInfo
|
||||
public bool IsScript { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Note: Config settings and settings-profiles are stored in the same files.
|
||||
/// </summary>
|
||||
public record ConfigResourceInfo : BaseResourceInfo, IConfigResourceInfo {}
|
||||
|
||||
public record ConfigProfileResourceInfo : BaseResourceInfo, IConfigProfileResourceInfo {}
|
||||
|
||||
public record LuaScriptsResourceInfo : BaseResourceInfo, ILuaScriptResourceInfo
|
||||
{
|
||||
public bool IsAutorun { get; init; }
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.LuaCs.Data;
|
||||
|
||||
public partial interface IModConfigInfo : IAssembliesResourcesInfo,
|
||||
ILuaScriptsResourcesInfo, IConfigsResourcesInfo,
|
||||
IConfigProfilesResourcesInfo
|
||||
ILuaScriptsResourcesInfo, IConfigsResourcesInfo
|
||||
{
|
||||
// package info
|
||||
ContentPackage Package { get; }
|
||||
}
|
||||
|
||||
public record ResourceParserInfo(
|
||||
ContentPackage Owner,
|
||||
XElement Element,
|
||||
[NotNull] ContentPackage Owner,
|
||||
[NotNull] XElement Element,
|
||||
ImmutableArray<Identifier> Required,
|
||||
ImmutableArray<Identifier> Incompatible);
|
||||
|
||||
@@ -9,8 +9,6 @@ public interface IBaseResourceInfo : IResourceInfo, IDataInfo, IDependencyInfo {
|
||||
|
||||
public interface IConfigResourceInfo : IBaseResourceInfo {}
|
||||
|
||||
public interface IConfigProfileResourceInfo :IBaseResourceInfo {}
|
||||
|
||||
/// <summary>
|
||||
/// Represents loadable Lua files.
|
||||
/// </summary>
|
||||
@@ -53,9 +51,4 @@ public interface IConfigsResourcesInfo
|
||||
ImmutableArray<IConfigResourceInfo> Configs { get; }
|
||||
}
|
||||
|
||||
public interface IConfigProfilesResourcesInfo
|
||||
{
|
||||
ImmutableArray<IConfigProfileResourceInfo> ConfigProfiles { get; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -112,7 +112,7 @@ public record StorageServiceConfig : IStorageServiceConfig, IStorageServiceConfi
|
||||
|
||||
public string LocalDataSavePath => Path.Combine(ExecutionLocation, "/Data/Mods/");
|
||||
|
||||
public string LocalDataPathRegex => "<PACKAGENAME>";
|
||||
public string LocalDataPathRegex => "%ModDir%";
|
||||
|
||||
public string RunLocation => ExecutionLocation;
|
||||
public bool GlobalSafeIOEnabled => false;
|
||||
|
||||
@@ -544,7 +544,6 @@ namespace Barotrauma
|
||||
async Task LoadStaticAssetsAsync(IReadOnlyList<ContentPackage> packages)
|
||||
{
|
||||
var cfgRes = ImmutableArray<IConfigResourceInfo>.Empty;
|
||||
var cfpRes = ImmutableArray<IConfigProfileResourceInfo>.Empty;
|
||||
var luaRes = ImmutableArray<ILuaScriptResourceInfo>.Empty;
|
||||
|
||||
var tasksBuilder = ImmutableArray.CreateBuilder<Task>();
|
||||
@@ -561,15 +560,6 @@ namespace Barotrauma
|
||||
res.ToResult());
|
||||
})(),
|
||||
new Func<Task>(async () =>
|
||||
{
|
||||
var res = await PackageManagementService.GetConfigProfilesInfosAsync(packages);
|
||||
if (res.IsSuccess)
|
||||
cfpRes = res.Value.ConfigProfiles;
|
||||
if (res.Errors.Any())
|
||||
ThreadPool.QueueUserWorkItem(state => Logger.LogResults((FluentResults.Result)state),
|
||||
res.ToResult());
|
||||
})(),
|
||||
new Func<Task>(async () =>
|
||||
{
|
||||
var res = await PackageManagementService.GetLuaScriptsInfosAsync(packages);
|
||||
if (res.IsSuccess)
|
||||
@@ -588,9 +578,6 @@ namespace Barotrauma
|
||||
var res = await ConfigService.LoadConfigsAsync(cfgRes);
|
||||
if (res.Errors.Any())
|
||||
Logger.LogResults(res);
|
||||
res = await ConfigService.LoadConfigsProfilesAsync(cfpRes);
|
||||
if (res.Errors.Any())
|
||||
Logger.LogResults(res);
|
||||
})(),
|
||||
new Func<Task>(async () =>
|
||||
{
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace Barotrauma.LuaCs.Services;
|
||||
public partial class ConfigService : IConfigService
|
||||
{
|
||||
//--- Internals
|
||||
public ConfigService(IParserServiceAsync<IConfigProfileResourceInfo, IReadOnlyList<IConfigProfileInfo>> configProfileResourceParser,
|
||||
public ConfigService(IParserServiceAsync<IConfigResourceInfo, IReadOnlyList<IConfigProfileInfo>> configProfileResourceParser,
|
||||
IParserServiceAsync<IConfigResourceInfo, IReadOnlyList<IConfigInfo>> configResourceParser, IEventService eventService, System.Lazy<IStorageService> storageService)
|
||||
{
|
||||
_configProfileResourceParser = configProfileResourceParser;
|
||||
@@ -48,7 +48,7 @@ public partial class ConfigService : IConfigService
|
||||
|
||||
// extern services
|
||||
private readonly IParserServiceAsync<IConfigResourceInfo, IReadOnlyList<IConfigInfo>> _configResourceParser;
|
||||
private readonly IParserServiceAsync<IConfigProfileResourceInfo, IReadOnlyList<IConfigProfileInfo>> _configProfileResourceParser;
|
||||
private readonly IParserServiceAsync<IConfigResourceInfo, IReadOnlyList<IConfigProfileInfo>> _configProfileResourceParser;
|
||||
private readonly IEventService _eventService;
|
||||
private readonly System.Lazy<IStorageService> _storageService;
|
||||
|
||||
@@ -357,7 +357,7 @@ public partial class ConfigService : IConfigService
|
||||
return ret;
|
||||
}
|
||||
|
||||
public async Task<FluentResults.Result> LoadConfigsProfilesAsync(ImmutableArray<IConfigProfileResourceInfo> configProfileResources)
|
||||
public async Task<FluentResults.Result> LoadConfigsProfilesAsync(ImmutableArray<IConfigResourceInfo> configProfileResources)
|
||||
{
|
||||
using var lck = await _disposeOpsLock.AcquireReaderLock();
|
||||
_base.CheckDisposed();
|
||||
|
||||
@@ -27,7 +27,6 @@ public partial class PackageManagementService : IPackageManagementService
|
||||
}
|
||||
|
||||
public ImmutableArray<IConfigResourceInfo> Configs { get; }
|
||||
public ImmutableArray<IConfigProfileResourceInfo> ConfigProfiles { get; }
|
||||
public ImmutableArray<ILuaScriptResourceInfo> LuaScripts { get; }
|
||||
public ImmutableArray<IAssemblyResourceInfo> Assemblies { get; }
|
||||
public async Task<FluentResults.Result> LoadPackageInfosAsync(ContentPackage package)
|
||||
@@ -75,11 +74,6 @@ public partial class PackageManagementService : IPackageManagementService
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public Result<IConfigProfilesResourcesInfo> GetConfigProfilesInfos(ContentPackage package, bool onlySupportedResources = true)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public Result<ILuaScriptsResourcesInfo> GetLuaScriptsInfos(ContentPackage package, bool onlySupportedResources = true)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
@@ -95,11 +89,6 @@ public partial class PackageManagementService : IPackageManagementService
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public Result<IConfigProfilesResourcesInfo> GetConfigProfilesInfos(IReadOnlyList<ContentPackage> packages, bool onlySupportedResources = true)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public Result<ILuaScriptsResourcesInfo> GetLuaScriptsInfos(IReadOnlyList<ContentPackage> packages, bool onlySupportedResources = true)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
@@ -115,11 +104,6 @@ public partial class PackageManagementService : IPackageManagementService
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public async Task<Result<IConfigProfilesResourcesInfo>> GetConfigProfilesInfosAsync(IReadOnlyList<ContentPackage> packages, bool onlySupportedResources = true)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public async Task<Result<ILuaScriptsResourcesInfo>> GetLuaScriptsInfosAsync(IReadOnlyList<ContentPackage> packages, bool onlySupportedResources = true)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
|
||||
@@ -0,0 +1,225 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.LuaCs.Data;
|
||||
using FluentResults;
|
||||
using Microsoft.Toolkit.Diagnostics;
|
||||
|
||||
namespace Barotrauma.LuaCs.Services.Processing;
|
||||
|
||||
public sealed class ConfigFileParserService :
|
||||
IParserServiceAsync<ResourceParserInfo, IAssemblyResourceInfo>,
|
||||
IParserServiceAsync<ResourceParserInfo, ILuaScriptResourceInfo>,
|
||||
IParserServiceAsync<ResourceParserInfo, IConfigResourceInfo>
|
||||
{
|
||||
private IStorageService _storageService;
|
||||
private readonly AsyncReaderWriterLock _operationsLock = new();
|
||||
|
||||
public ConfigFileParserService(IStorageService storageService)
|
||||
{
|
||||
_storageService = storageService;
|
||||
}
|
||||
|
||||
#region Dispose
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
using var lck = _operationsLock.AcquireWriterLock().ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
if (!ModUtils.Threading.CheckIfClearAndSetBool(ref _isDisposed))
|
||||
return;
|
||||
try
|
||||
{
|
||||
_storageService.Dispose();
|
||||
this._storageService = null;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
private int _isDisposed = 0;
|
||||
public bool IsDisposed
|
||||
{
|
||||
get => ModUtils.Threading.GetBool(ref _isDisposed);
|
||||
private set => ModUtils.Threading.SetBool(ref _isDisposed, value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
// --- Assemblies
|
||||
async Task<Result<IAssemblyResourceInfo>> IParserServiceAsync<ResourceParserInfo, IAssemblyResourceInfo>.TryParseResourceAsync(ResourceParserInfo src)
|
||||
{
|
||||
using var lck = await _operationsLock.AcquireWriterLock();
|
||||
IService.CheckDisposed(this);
|
||||
|
||||
if (CheckThrowNullRefs(src, "Assembly") is { IsFailed: true } fail)
|
||||
return fail;
|
||||
|
||||
var runtimeEnv = GetRuntimeEnvironment(src.Element);
|
||||
var fileResults = await GetCheckedFiles(src.Element, src.Owner, ".dll");
|
||||
|
||||
if (fileResults.IsFailed)
|
||||
return FluentResults.Result.Fail(fileResults.Errors);
|
||||
|
||||
return new AssemblyResourceInfo()
|
||||
{
|
||||
SupportedPlatforms = runtimeEnv.Platform,
|
||||
SupportedTargets = runtimeEnv.Target,
|
||||
LoadPriority = src.Element.GetAttributeInt("LoadPriority", 0),
|
||||
FilePaths = fileResults.Value,
|
||||
Optional = src.Element.GetAttributeBool("Optional", false),
|
||||
InternalName = src.Element.GetAttributeString("Name", string.Empty),
|
||||
OwnerPackage = src.Owner,
|
||||
RequiredPackages = src.Required,
|
||||
IncompatiblePackages = src.Incompatible,
|
||||
// Type Specific
|
||||
FriendlyName = src.Element.GetAttributeString("FriendlyName", string.Empty),
|
||||
IsScript = src.Element.GetAttributeBool("IsScript", false),
|
||||
};
|
||||
}
|
||||
|
||||
async Task<ImmutableArray<Result<IAssemblyResourceInfo>>> IParserServiceAsync<ResourceParserInfo, IAssemblyResourceInfo>.TryParseResourcesAsync(IEnumerable<ResourceParserInfo> sources)
|
||||
{
|
||||
return await this.TryParseGenericResourcesAsync<IAssemblyResourceInfo>(sources);
|
||||
}
|
||||
|
||||
// --- Config
|
||||
|
||||
async Task<Result<IConfigResourceInfo>> IParserServiceAsync<ResourceParserInfo, IConfigResourceInfo>.TryParseResourceAsync(ResourceParserInfo src)
|
||||
{
|
||||
|
||||
using var lck = await _operationsLock.AcquireWriterLock();
|
||||
IService.CheckDisposed(this);
|
||||
|
||||
if (CheckThrowNullRefs(src, "Config") is { IsFailed: true } fail)
|
||||
return fail;
|
||||
|
||||
var runtimeEnv = GetRuntimeEnvironment(src.Element);
|
||||
var fileResults = await GetCheckedFiles(src.Element, src.Owner, ".xml");
|
||||
|
||||
if (fileResults.IsFailed)
|
||||
return FluentResults.Result.Fail(fileResults.Errors);
|
||||
|
||||
return new ConfigResourceInfo()
|
||||
{
|
||||
SupportedPlatforms = runtimeEnv.Platform,
|
||||
SupportedTargets = runtimeEnv.Target,
|
||||
LoadPriority = src.Element.GetAttributeInt("LoadPriority", 0),
|
||||
FilePaths = fileResults.Value,
|
||||
Optional = src.Element.GetAttributeBool("Optional", false),
|
||||
InternalName = src.Element.GetAttributeString("Name", string.Empty),
|
||||
OwnerPackage = src.Owner,
|
||||
RequiredPackages = src.Required,
|
||||
IncompatiblePackages = src.Incompatible
|
||||
};
|
||||
}
|
||||
|
||||
async Task<ImmutableArray<Result<IConfigResourceInfo>>> IParserServiceAsync<ResourceParserInfo, IConfigResourceInfo>.TryParseResourcesAsync(IEnumerable<ResourceParserInfo> sources)
|
||||
{
|
||||
return await this.TryParseGenericResourcesAsync<IConfigResourceInfo>(sources);
|
||||
}
|
||||
|
||||
// --- Lua Scripts
|
||||
async Task<Result<ILuaScriptResourceInfo>> IParserServiceAsync<ResourceParserInfo, ILuaScriptResourceInfo>.TryParseResourceAsync(ResourceParserInfo src)
|
||||
{
|
||||
using var lck = await _operationsLock.AcquireWriterLock();
|
||||
IService.CheckDisposed(this);
|
||||
|
||||
if (CheckThrowNullRefs(src, "Lua") is { IsFailed: true } fail)
|
||||
return fail;
|
||||
|
||||
var runtimeEnv = GetRuntimeEnvironment(src.Element);
|
||||
var fileResults = await GetCheckedFiles(src.Element, src.Owner, ".lua");
|
||||
|
||||
if (fileResults.IsFailed)
|
||||
return FluentResults.Result.Fail(fileResults.Errors);
|
||||
|
||||
return new LuaScriptsResourceInfo()
|
||||
{
|
||||
SupportedPlatforms = runtimeEnv.Platform,
|
||||
SupportedTargets = runtimeEnv.Target,
|
||||
LoadPriority = src.Element.GetAttributeInt("LoadPriority", 0),
|
||||
FilePaths = fileResults.Value,
|
||||
Optional = src.Element.GetAttributeBool("Optional", false),
|
||||
InternalName = src.Element.GetAttributeString("Name", string.Empty),
|
||||
OwnerPackage = src.Owner,
|
||||
RequiredPackages = src.Required,
|
||||
IncompatiblePackages = src.Incompatible,
|
||||
// Type Specific
|
||||
IsAutorun = src.Element.GetAttributeBool("RunFile", false)
|
||||
};
|
||||
}
|
||||
|
||||
private FluentResults.Result CheckThrowNullRefs(ResourceParserInfo src, string elementName)
|
||||
{
|
||||
Guard.IsNotNull(src, nameof(src));
|
||||
Guard.IsNotNull(src.Owner, nameof(src.Owner));
|
||||
Guard.IsNotNull(src.Element, nameof(src.Element));
|
||||
|
||||
if (src.Element.Name != elementName)
|
||||
{
|
||||
return FluentResults.Result.Fail($"Element name '{elementName}' is incorrect");
|
||||
}
|
||||
|
||||
return FluentResults.Result.Ok();
|
||||
}
|
||||
|
||||
async Task<ImmutableArray<Result<ILuaScriptResourceInfo>>> IParserServiceAsync<ResourceParserInfo, ILuaScriptResourceInfo>.TryParseResourcesAsync(IEnumerable<ResourceParserInfo> sources)
|
||||
{
|
||||
return await this.TryParseGenericResourcesAsync<ILuaScriptResourceInfo>(sources);
|
||||
}
|
||||
|
||||
// --- Helpers
|
||||
private async Task<Result<ImmutableArray<ContentPath>>> GetCheckedFiles(XElement srcElement, ContentPackage srcOwner, string fileExtension)
|
||||
{
|
||||
using var lck = await _operationsLock.AcquireWriterLock();
|
||||
IService.CheckDisposed(this);
|
||||
|
||||
var builder = ImmutableArray.CreateBuilder<ContentPath>();
|
||||
var filePath = srcElement.GetAttributeString("File", string.Empty);
|
||||
var folderPath = srcElement.GetAttributeString("Folder", string.Empty);
|
||||
|
||||
if (!filePath.IsNullOrWhiteSpace())
|
||||
{
|
||||
var cp = ContentPath.FromRaw(srcOwner, filePath);
|
||||
if (_storageService.FileExists(cp.FullPath) is { IsSuccess: true, Value: true })
|
||||
{
|
||||
builder.Add(cp);
|
||||
}
|
||||
}
|
||||
|
||||
if (!folderPath.IsNullOrWhiteSpace())
|
||||
{
|
||||
var cp = ContentPath.FromRaw(srcOwner, folderPath);
|
||||
if (_storageService.DirectoryExists(cp.FullPath) is { IsSuccess: true, Value: true })
|
||||
{
|
||||
var files = _storageService.FindFilesInPackage(cp.ContentPackage, cp.Value, fileExtension, true);
|
||||
}
|
||||
}
|
||||
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
private (Platform Platform, Target Target) GetRuntimeEnvironment(XElement element)
|
||||
{
|
||||
return (
|
||||
Platform: element.GetAttributeEnum("Platform", Platform.Windows | Platform.Linux | Platform.OSX),
|
||||
Target: element.GetAttributeEnum("Target", Target.Client | Target.Server));
|
||||
}
|
||||
|
||||
private async Task<ImmutableArray<Result<T>>> TryParseGenericResourcesAsync<T>(IEnumerable<ResourceParserInfo> sources)
|
||||
{
|
||||
// ReSharper disable once PossibleMultipleEnumeration
|
||||
Guard.IsNotNull(sources, nameof(IParserServiceAsync<ResourceParserInfo, T>.TryParseResourcesAsync));
|
||||
var builder = ImmutableArray.CreateBuilder<Result<T>>();
|
||||
foreach (var info in sources)
|
||||
{
|
||||
builder.Add(await Unsafe.As<IParserServiceAsync<ResourceParserInfo, T>>(this).TryParseResourceAsync(info));
|
||||
}
|
||||
return builder.ToImmutable();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,6 +3,7 @@ using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -17,29 +18,51 @@ namespace Barotrauma.LuaCs.Services.Processing;
|
||||
public sealed class ModConfigService : IModConfigService
|
||||
{
|
||||
private IStorageService _storageService;
|
||||
private ILoggerService _logger;
|
||||
private IParserServiceAsync<ResourceParserInfo, IAssemblyResourceInfo> _assemblyParserService;
|
||||
private IParserServiceAsync<ResourceParserInfo, ILuaScriptResourceInfo> _luaScriptParserService;
|
||||
private IParserServiceAsync<ResourceParserInfo, IConfigResourceInfo> _configParserService;
|
||||
private IParserServiceAsync<ResourceParserInfo, IConfigProfileResourceInfo> _configProfileParserService;
|
||||
private readonly AsyncReaderWriterLock _operationsLock = new();
|
||||
|
||||
public ModConfigService(IStorageService storageService,
|
||||
IParserServiceAsync<ResourceParserInfo, IAssemblyResourceInfo> assemblyParserService,
|
||||
IParserServiceAsync<ResourceParserInfo, ILuaScriptResourceInfo> luaScriptParserService,
|
||||
IParserServiceAsync<ResourceParserInfo, IConfigResourceInfo> configParserService,
|
||||
IParserServiceAsync<ResourceParserInfo, IConfigProfileResourceInfo> configProfileParserService)
|
||||
ILoggerService logger)
|
||||
{
|
||||
_storageService = storageService;
|
||||
_assemblyParserService = assemblyParserService;
|
||||
_luaScriptParserService = luaScriptParserService;
|
||||
_configParserService = configParserService;
|
||||
_configProfileParserService = configProfileParserService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
#region Dispose
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
using var lck = _operationsLock.AcquireWriterLock().ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
if (!ModUtils.Threading.CheckIfClearAndSetBool(ref _isDisposed))
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
_storageService.Dispose();
|
||||
_logger.Dispose();
|
||||
_assemblyParserService.Dispose();
|
||||
_luaScriptParserService.Dispose();
|
||||
_configParserService.Dispose();
|
||||
|
||||
_storageService = null;
|
||||
_logger = null;
|
||||
_assemblyParserService = null;
|
||||
_luaScriptParserService = null;
|
||||
_configParserService = null;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
private int _isDisposed = 0;
|
||||
@@ -54,6 +77,8 @@ public sealed class ModConfigService : IModConfigService
|
||||
public async Task<Result<IModConfigInfo>> CreateConfigAsync(ContentPackage src)
|
||||
{
|
||||
Guard.IsNotNull(src, nameof(src));
|
||||
using var lck = await _operationsLock.AcquireReaderLock();
|
||||
IService.CheckDisposed(this);
|
||||
|
||||
if (await TryGetModConfigXmlAsync(src) is { IsSuccess: true, Value: { } config })
|
||||
{
|
||||
@@ -65,6 +90,11 @@ public sealed class ModConfigService : IModConfigService
|
||||
|
||||
public async Task<ImmutableArray<(ContentPackage Source, Result<IModConfigInfo> Config)>> CreateConfigsAsync(ImmutableArray<ContentPackage> src)
|
||||
{
|
||||
if (src.IsDefaultOrEmpty)
|
||||
ThrowHelper.ThrowArgumentNullException($"{nameof(CreateConfigsAsync)}: The supplied array is default or empty!");
|
||||
using var lck = await _operationsLock.AcquireReaderLock();
|
||||
IService.CheckDisposed(this);
|
||||
|
||||
var builder = new ConcurrentQueue<(ContentPackage Source, Result<IModConfigInfo> Config)>();
|
||||
|
||||
await src.ParallelForEachAsync(async package =>
|
||||
@@ -79,55 +109,125 @@ public sealed class ModConfigService : IModConfigService
|
||||
//--- Helpers
|
||||
private async Task<Result<XElement>> TryGetModConfigXmlAsync(ContentPackage src)
|
||||
{
|
||||
return await _storageService.LoadPackageXmlAsync(src, "ModConfig.xml") is { IsSuccess: true, Value: { Root: {} config} }
|
||||
return await _storageService.LoadPackageXmlAsync(ContentPath.FromRaw(src, "%ModDir%/ModConfig.xml")) is { IsSuccess: true, Value: { Root: {} config} }
|
||||
? FluentResults.Result.Ok(config)
|
||||
: FluentResults.Result.Fail<XElement>("ModConfig.xml not found");
|
||||
}
|
||||
|
||||
private async Task<Result<IModConfigInfo>> CreateFromConfigXmlAsync(ContentPackage owner, XElement src)
|
||||
{
|
||||
/*var cfg = src.GetChildElements("Config");
|
||||
var modConfig = new ModConfigInfo()
|
||||
ImmutableArray<IAssemblyResourceInfo> assemblyResources = default;
|
||||
ImmutableArray<IConfigResourceInfo> configResources = default;
|
||||
ImmutableArray<ILuaScriptResourceInfo> luaResources = default;
|
||||
|
||||
var res = await Task.WhenAll(new[]
|
||||
{
|
||||
new Task<Task>(async () => assemblyResources = await GetAssembliesFromXml(owner, src)),
|
||||
new Task<Task>(async () => configResources = await GetConfigsFromXml(owner, src)),
|
||||
new Task<Task>(async () => luaResources = await GetLuaScriptsFromXml(owner, src)),
|
||||
});
|
||||
|
||||
bool isFaulted = false;
|
||||
foreach (var task in res)
|
||||
{
|
||||
if (task.IsFaulted)
|
||||
{
|
||||
_logger.LogError($"{nameof(CreateFromConfigXmlAsync)}: {task.Exception?.ToString()}");
|
||||
isFaulted = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (isFaulted)
|
||||
{
|
||||
_logger.LogError($"{nameof(CreateFromConfigXmlAsync)}: Failed to process content package: {owner.Name}");
|
||||
return FluentResults.Result.Fail($"{nameof(CreateFromConfigXmlAsync)}: Failed to process content package: {owner.Name}");
|
||||
}
|
||||
|
||||
return FluentResults.Result.Ok<IModConfigInfo>(new ModConfigInfo()
|
||||
{
|
||||
Package = owner,
|
||||
Assemblies = src.GetChildElements("Assembly") is {} asm ? GetAssembliesFromXml(owner, asm)
|
||||
: ImmutableArray<IAssemblyResourceInfo>.Empty,
|
||||
Configs = cfg is {} ? GetConfigsFromXml(owner, cfg) : ImmutableArray<IConfigResourceInfo>.Empty,
|
||||
ConfigProfiles = cfg is {} ? GetConfigProfilesFromXml(owner, cfg) : ImmutableArray<IConfigProfileResourceInfo>.Empty,
|
||||
LuaScripts = src.GetChildElements("Lua") is {} lua ? GetLuaScriptsFromXml(owner, lua)
|
||||
: ImmutableArray<ILuaScriptResourceInfo>.Empty
|
||||
};*/
|
||||
Assemblies = assemblyResources,
|
||||
Configs = configResources,
|
||||
LuaScripts = luaResources
|
||||
});
|
||||
|
||||
async Task<FluentResults.Result<ImmutableArray<ILuaScriptResourceInfo>>> GetLuaScriptsFromXml(ContentPackage contentPackage,
|
||||
async Task<ImmutableArray<ILuaScriptResourceInfo>> GetLuaScriptsFromXml(ContentPackage contentPackage,
|
||||
XElement cfgElement)
|
||||
{
|
||||
var luaElems = cfgElement.GetChildElements("Lua").ToImmutableArray();
|
||||
if (cfgElement.GetChildElements("FileGroup").ToImmutableArray() is { IsDefaultOrEmpty: false } fileGroup
|
||||
&& fileGroup.SelectMany(fg => fg.GetChildElements()))
|
||||
return await GetResourceFromXml<ILuaScriptResourceInfo>(contentPackage, cfgElement, "Lua", "FileGroup", _luaScriptParserService);
|
||||
}
|
||||
|
||||
async Task<ImmutableArray<IConfigResourceInfo>> GetConfigsFromXml(ContentPackage contentPackage,
|
||||
XElement cfgElement)
|
||||
{
|
||||
return await GetResourceFromXml<IConfigResourceInfo>(contentPackage, cfgElement, "Config", "FileGroup", _configParserService);
|
||||
|
||||
}
|
||||
|
||||
async Task<ImmutableArray<IAssemblyResourceInfo>> GetAssembliesFromXml(ContentPackage contentPackage,
|
||||
XElement cfgElement)
|
||||
{
|
||||
return await GetResourceFromXml<IAssemblyResourceInfo>(contentPackage, cfgElement, "Assembly", "FileGroup", _assemblyParserService);
|
||||
}
|
||||
|
||||
async Task<ImmutableArray<T>> GetResourceFromXml<T>(ContentPackage contentPackage, XElement cfgElement, string elemName, string fileGroupName, IParserServiceAsync<ResourceParserInfo, T> resourceService)
|
||||
{
|
||||
var elems = GetResourceElementsWithName(owner, cfgElement, elemName, fileGroupName);
|
||||
if (elems.IsDefaultOrEmpty)
|
||||
return ImmutableArray<T>.Empty;
|
||||
|
||||
var results = await resourceService.TryParseResourcesAsync(elems);
|
||||
Guard.IsNotEmpty((IReadOnlyCollection<Result<T>>)results, nameof(results));
|
||||
|
||||
var resources = ImmutableArray.CreateBuilder<T>();
|
||||
foreach (var result in results)
|
||||
{
|
||||
|
||||
if (result.Errors.Count > 0)
|
||||
{
|
||||
_logger.LogResults(result.ToResult());
|
||||
continue;
|
||||
}
|
||||
resources.Add(result.Value);
|
||||
}
|
||||
return resources.MoveToImmutable();
|
||||
}
|
||||
|
||||
ImmutableArray<ResourceParserInfo> GetResourceElementsWithName(ContentPackage package, XElement root, string elemName, string groupName)
|
||||
{
|
||||
var elems = ImmutableArray.CreateBuilder<ResourceParserInfo>();
|
||||
|
||||
elems.AddRange(root.GetChildElements(elemName)
|
||||
.Select(e => new ResourceParserInfo(package, e, ImmutableArray<Identifier>.Empty, ImmutableArray<Identifier>.Empty))
|
||||
.ToImmutableArray());
|
||||
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
if (root.GetChildElements(groupName).ToImmutableArray() is { IsDefaultOrEmpty: false } fileGroups)
|
||||
{
|
||||
foreach (var fileGroup in fileGroups)
|
||||
{
|
||||
if (fileGroup.GetChildElements(elemName).ToImmutableArray() is { IsDefaultOrEmpty: false } subLuaElems)
|
||||
{
|
||||
var cond = GetDependencyIdentifiers(fileGroup, true);
|
||||
var negCond = GetDependencyIdentifiers(fileGroup, false);
|
||||
|
||||
async Task<FluentResults.Result<ImmutableArray<IConfigProfileResourceInfo>>> GetConfigProfilesFromXml(ContentPackage contentPackage,
|
||||
XElement cfgElement)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
foreach (var element in subLuaElems)
|
||||
{
|
||||
elems.Add(new ResourceParserInfo(package, element, cond, negCond));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async Task<FluentResults.Result<ImmutableArray<IConfigResourceInfo>>> GetConfigsFromXml(ContentPackage contentPackage,
|
||||
XElement cfgElement)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
return elems.MoveToImmutable();
|
||||
}
|
||||
|
||||
async Task<FluentResults.Result<ImmutableArray<IAssemblyResourceInfo>>> GetAssembliesFromXml(ContentPackage contentPackage,
|
||||
XElement cfgElement)
|
||||
|
||||
ImmutableArray<Identifier> GetDependencyIdentifiers(XElement fg, bool depsLoadedSetting)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
return fg.GetChildElements("Conditional")
|
||||
.Where(cElem => bool.TryParse(cElem.GetAttribute("IsLoaded").Value, out bool isLoaded) && isLoaded == depsLoadedSetting)
|
||||
.SelectMany(cElem2 => cElem2.GetAttributeString("Dependencies", String.Empty)
|
||||
.Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(ident => new Identifier(ident)))
|
||||
.ToImmutableArray();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,52 +2,52 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Security;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.LuaCs.Data;
|
||||
using Barotrauma.Steam;
|
||||
using FluentResults;
|
||||
using FluentResults.LuaCs;
|
||||
using Microsoft.Toolkit.Diagnostics;
|
||||
using Error = FluentResults.Error;
|
||||
using Path = Barotrauma.IO.Path;
|
||||
using Path = System.IO.Path;
|
||||
|
||||
namespace Barotrauma.LuaCs.Services;
|
||||
|
||||
public class StorageService : IStorageService
|
||||
{
|
||||
|
||||
public StorageService(IStorageServiceConfig configData)
|
||||
{
|
||||
_configData = configData;
|
||||
ConfigData = configData;
|
||||
}
|
||||
|
||||
private readonly ConcurrentDictionary<string, OneOf.OneOf<byte[], string, XDocument>> _fsCache = new();
|
||||
protected readonly IStorageServiceConfig _configData;
|
||||
|
||||
protected readonly IStorageServiceConfig ConfigData;
|
||||
protected readonly AsyncReaderWriterLock OperationsLock = new();
|
||||
|
||||
public bool IsDisposed => ModUtils.Threading.GetBool(ref _isDisposed);
|
||||
private int _isDisposed = 0;
|
||||
public void Dispose()
|
||||
{
|
||||
ModUtils.Threading.SetBool(ref _isDisposed, true);
|
||||
if (!ModUtils.Threading.CheckIfClearAndSetBool(ref _isDisposed))
|
||||
return;
|
||||
using var lck = OperationsLock.AcquireWriterLock().ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
_fsCache.Clear();
|
||||
}
|
||||
|
||||
public void PurgeCache()
|
||||
{
|
||||
((IService)this).CheckDisposed();
|
||||
using var lck = OperationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
IService.CheckDisposed(this);
|
||||
_fsCache.Clear();
|
||||
}
|
||||
|
||||
public void PurgeFileFromCache(string absolutePath)
|
||||
{
|
||||
((IService)this).CheckDisposed();
|
||||
using var lck = OperationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
IService.CheckDisposed(this);
|
||||
|
||||
if (absolutePath.IsNullOrWhiteSpace())
|
||||
return;
|
||||
@@ -67,7 +67,8 @@ public class StorageService : IStorageService
|
||||
|
||||
public void PurgeFilesFromCache(params string[] absolutePaths)
|
||||
{
|
||||
((IService)this).CheckDisposed();
|
||||
using var lck = OperationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
IService.CheckDisposed(this);
|
||||
|
||||
if (absolutePaths.Length < 1)
|
||||
return;
|
||||
@@ -91,6 +92,201 @@ public class StorageService : IStorageService
|
||||
}
|
||||
}
|
||||
|
||||
// --- Local Game Content
|
||||
protected Result<string> GetAbsolutePathForLocal(ContentPackage package, string localFilePath)
|
||||
{
|
||||
if (Path.IsPathRooted(localFilePath))
|
||||
ThrowHelper.ThrowArgumentException($"{nameof(GetAbsolutePathForLocal)}: The path {localFilePath} is an absolute path.");
|
||||
|
||||
try
|
||||
{
|
||||
var path = System.IO.Path.GetFullPath(Path.Combine(
|
||||
ConfigData.LocalPackageDataPath.Replace(ConfigData.LocalDataPathRegex, package.ToIdentifier().Value)
|
||||
.CleanUpPathCrossPlatform(),
|
||||
localFilePath));
|
||||
if (!path.StartsWith(ConfigData.LocalDataSavePath))
|
||||
ThrowHelper.ThrowUnauthorizedAccessException($"{nameof(GetAbsolutePathForLocal)}: The local path of '{path}' is not a local path!");
|
||||
return path;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (e is ArgumentNullException or ArgumentException or UnauthorizedAccessException)
|
||||
throw; // these are dev errors and should be propagated.
|
||||
return FluentResults.Result.Fail(new ExceptionalError(e));
|
||||
}
|
||||
}
|
||||
|
||||
private Result<T> LoadLocalData<T>(ContentPackage package, string localFilePath, Func<string, Result<T>> dataLoader)
|
||||
{
|
||||
Guard.IsNotNull(package, nameof(package));
|
||||
Guard.IsNotNullOrWhiteSpace(localFilePath, nameof(localFilePath));
|
||||
using var lck = OperationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
IService.CheckDisposed(this);
|
||||
var res = GetAbsolutePathForLocal(package, localFilePath);
|
||||
return res is { IsFailed: true } ? res.ToResult() : dataLoader(res.Value);
|
||||
}
|
||||
|
||||
public Result<XDocument> LoadLocalXml(ContentPackage package, string localFilePath) => LoadLocalData(package, localFilePath, TryLoadXml);
|
||||
public Result<byte[]> LoadLocalBinary(ContentPackage package, string localFilePath) => LoadLocalData(package, localFilePath, TryLoadBinary);
|
||||
public Result<string> LoadLocalText(ContentPackage package, string localFilePath) => LoadLocalData(package, localFilePath, TryLoadText);
|
||||
|
||||
|
||||
private FluentResults.Result SaveLocalData<T>(ContentPackage package, string localFilePath, in T data, Func<string, T, FluentResults.Result> dataSaver)
|
||||
{
|
||||
Guard.IsNotNull(package, nameof(package));
|
||||
Guard.IsNotNullOrWhiteSpace(localFilePath, nameof(localFilePath));
|
||||
using var lck = OperationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
IService.CheckDisposed(this);
|
||||
var res = GetAbsolutePathForLocal(package, localFilePath);
|
||||
return res is { IsFailed: true } ? res.ToResult() : dataSaver(res.Value, data);
|
||||
}
|
||||
|
||||
public FluentResults.Result SaveLocalXml(ContentPackage package, string localFilePath, XDocument document)
|
||||
=> SaveLocalData(package, localFilePath, document, (path, data) => TrySaveXml(path, in data));
|
||||
public FluentResults.Result SaveLocalBinary(ContentPackage package, string localFilePath, in byte[] bytes)
|
||||
=> SaveLocalData(package, localFilePath, bytes, (path, data) => TrySaveBinary(path, in data));
|
||||
public FluentResults.Result SaveLocalText(ContentPackage package, string localFilePath, in string text)
|
||||
=> SaveLocalData(package, localFilePath, text, (path, data) => TrySaveText(path, in data));
|
||||
|
||||
private async Task<Result<T>> LoadLocalDataAsync<T>(ContentPackage package, string localFilePath,
|
||||
Func<string, Task<Result<T>>> dataLoader)
|
||||
{
|
||||
Guard.IsNotNull(package, nameof(package));
|
||||
Guard.IsNotNullOrWhiteSpace(localFilePath, nameof(localFilePath));
|
||||
using var lck = await OperationsLock.AcquireReaderLock();
|
||||
IService.CheckDisposed(this);
|
||||
var res = GetAbsolutePathForLocal(package, localFilePath);
|
||||
return res is { IsFailed: true } ? res.ToResult() : await dataLoader(res.Value);
|
||||
}
|
||||
|
||||
public async Task<Result<XDocument>> LoadLocalXmlAsync(ContentPackage package, string localFilePath)
|
||||
=> await LoadLocalDataAsync(package, localFilePath, async path => await TryLoadXmlAsync(path));
|
||||
public async Task<Result<byte[]>> LoadLocalBinaryAsync(ContentPackage package, string localFilePath)
|
||||
=> await LoadLocalDataAsync(package, localFilePath, async path => await TryLoadBinaryAsync(path));
|
||||
public async Task<Result<string>> LoadLocalTextAsync(ContentPackage package, string localFilePath)
|
||||
=> await LoadLocalDataAsync(package, localFilePath, async path => await TryLoadTextAsync(path));
|
||||
|
||||
private async Task<FluentResults.Result> SaveLocalDataAsync<T>(ContentPackage package, string localFilePath,
|
||||
T data, Func<string, T, Task<FluentResults.Result>> dataSaver)
|
||||
{
|
||||
Guard.IsNotNull(package, nameof(package));
|
||||
Guard.IsNotNullOrWhiteSpace(localFilePath, nameof(localFilePath));
|
||||
IService.CheckDisposed(this);
|
||||
using var lck = await OperationsLock.AcquireReaderLock();
|
||||
var res = GetAbsolutePathForLocal(package, localFilePath);
|
||||
return res is { IsFailed: true } ? res.ToResult() : await dataSaver(res.Value, data);
|
||||
}
|
||||
|
||||
public async Task<FluentResults.Result> SaveLocalXmlAsync(ContentPackage package, string localFilePath, XDocument document)
|
||||
=> await SaveLocalDataAsync(package, localFilePath, document, async (path, doc) => await TrySaveXmlAsync(path, doc));
|
||||
public async Task<FluentResults.Result> SaveLocalBinaryAsync(ContentPackage package, string localFilePath, byte[] bytes)
|
||||
=> await SaveLocalDataAsync(package, localFilePath, bytes, async (path, bin) => await TrySaveBinaryAsync(path, bin));
|
||||
public async Task<FluentResults.Result> SaveLocalTextAsync(ContentPackage package, string localFilePath, string text)
|
||||
=> await SaveLocalDataAsync(package, localFilePath, text, async (path, txt) => await TrySaveTextAsync(path, txt));
|
||||
|
||||
|
||||
// --- Package Content
|
||||
private Result<T> LoadPackageData<T>(ContentPath filePath, Func<string, Result<T>> dataLoader)
|
||||
{
|
||||
Guard.IsNotNull(filePath, nameof(filePath));
|
||||
Guard.IsNotNullOrWhiteSpace(filePath.FullPath, nameof(filePath.FullPath));
|
||||
using var lck = OperationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
IService.CheckDisposed(this);
|
||||
if (!filePath.FullPath.StartsWith(ConfigData.WorkshopModsDirectory) && !filePath.FullPath.StartsWith(ConfigData.LocalModsDirectory))
|
||||
ThrowHelper.ThrowUnauthorizedAccessException($"{nameof(LoadPackageData)}: The filepath of `{filePath.FullPath}' is not in a package directory!");
|
||||
return dataLoader(filePath.FullPath);
|
||||
}
|
||||
|
||||
public Result<XDocument> LoadPackageXml(ContentPath filePath)
|
||||
=> LoadPackageData(filePath, path => TryLoadXml(filePath.FullPath));
|
||||
public Result<byte[]> LoadPackageBinary(ContentPath filePath)
|
||||
=> LoadPackageData(filePath, path => TryLoadBinary(filePath.FullPath));
|
||||
public Result<string> LoadPackageText(ContentPath filePath)
|
||||
=> LoadPackageData(filePath, path => TryLoadText(filePath.FullPath));
|
||||
|
||||
private ImmutableArray<(ContentPath, Result<T>)> LoadPackageDataFiles<T>(ImmutableArray<ContentPath> filePaths, Func<string, Result<T>> dataLoader)
|
||||
{
|
||||
if (filePaths.IsDefaultOrEmpty)
|
||||
ThrowHelper.ThrowArgumentNullException($"{nameof(LoadPackageData)}: File paths is empty!");
|
||||
using var lck = OperationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
var builder = ImmutableArray.CreateBuilder<(ContentPath, Result<T>)>();
|
||||
foreach (var path in filePaths)
|
||||
{
|
||||
builder.Add((path, LoadPackageData(path, dataLoader)));
|
||||
}
|
||||
return builder.MoveToImmutable();
|
||||
}
|
||||
|
||||
public ImmutableArray<(ContentPath, Result<XDocument>)> LoadPackageXmlFiles(ImmutableArray<ContentPath> filePaths)
|
||||
=> LoadPackageDataFiles(filePaths, TryLoadXml);
|
||||
public ImmutableArray<(ContentPath, Result<byte[]>)> LoadPackageBinaryFiles(ImmutableArray<ContentPath> filePaths)
|
||||
=> LoadPackageDataFiles(filePaths, TryLoadBinary);
|
||||
public ImmutableArray<(ContentPath, Result<string>)> LoadPackageTextFiles(ImmutableArray<ContentPath> filePaths)
|
||||
=> LoadPackageDataFiles(filePaths, TryLoadText);
|
||||
|
||||
public Result<ImmutableArray<string>> FindFilesInPackage(ContentPackage package, string localSubfolder, string regexFilter, bool searchRecursively)
|
||||
{
|
||||
Guard.IsNotNull(package, nameof(package));
|
||||
try
|
||||
{
|
||||
var fullPath = localSubfolder.IsNullOrWhiteSpace()
|
||||
? Path.GetFullPath(package.Path)
|
||||
: Path.GetFullPath(package.Path, localSubfolder);
|
||||
return System.IO.Directory.GetFiles(fullPath, regexFilter,
|
||||
searchRecursively ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly)
|
||||
.ToImmutableArray();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (e is ArgumentNullException or ArgumentException)
|
||||
throw;
|
||||
return FluentResults.Result.Fail(new ExceptionalError(e)
|
||||
.WithMetadata(MetadataType.ExceptionObject, this)
|
||||
.WithMetadata(MetadataType.RootObject, package));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private async Task<Result<T>> LoadPackageDataAsync<T>(ContentPath filePath, Func<string, Task<Result<T>>> dataLoader)
|
||||
{
|
||||
Guard.IsNotNull(filePath, nameof(filePath));
|
||||
Guard.IsNotNullOrWhiteSpace(filePath.FullPath, nameof(filePath.FullPath));
|
||||
using var lck = await OperationsLock.AcquireReaderLock();
|
||||
IService.CheckDisposed(this);
|
||||
if (!filePath.FullPath.StartsWith(ConfigData.WorkshopModsDirectory) && !filePath.FullPath.StartsWith(ConfigData.LocalModsDirectory))
|
||||
ThrowHelper.ThrowUnauthorizedAccessException($"{nameof(LoadPackageData)}: The filepath of `{filePath.FullPath}' is not in a package directory!");
|
||||
return await dataLoader(filePath.FullPath);
|
||||
}
|
||||
|
||||
public async Task<Result<XDocument>> LoadPackageXmlAsync(ContentPath filePath)
|
||||
=> await LoadPackageDataAsync(filePath, async path => await TryLoadXmlAsync(path));
|
||||
public async Task<Result<byte[]>> LoadPackageBinaryAsync(ContentPath filePath)
|
||||
=> await LoadPackageDataAsync(filePath, async path => await TryLoadBinaryAsync(path));
|
||||
public async Task<Result<string>> LoadPackageTextAsync(ContentPath filePath)
|
||||
=> await LoadPackageDataAsync(filePath, async path => await TryLoadTextAsync(path));
|
||||
|
||||
private async Task<ImmutableArray<(ContentPath, Result<T>)>> LoadPackageDataFilesAsync<T>(
|
||||
ImmutableArray<ContentPath> filePaths, Func<string, Task<Result<T>>> dataLoader)
|
||||
{
|
||||
if (filePaths.IsDefaultOrEmpty)
|
||||
ThrowHelper.ThrowArgumentNullException($"{nameof(LoadPackageData)}: File paths is empty!");
|
||||
using var lck = await OperationsLock.AcquireReaderLock();
|
||||
var builder = ImmutableArray.CreateBuilder<(ContentPath, Result<T>)>();
|
||||
foreach (var path in filePaths)
|
||||
{
|
||||
builder.Add((path, await LoadPackageDataAsync(path, dataLoader)));
|
||||
}
|
||||
return builder.MoveToImmutable();
|
||||
}
|
||||
|
||||
public async Task<ImmutableArray<(ContentPath, Result<XDocument>)>> LoadPackageXmlFilesAsync(ImmutableArray<ContentPath> filePaths)
|
||||
=> await LoadPackageDataFilesAsync(filePaths, async path => await TryLoadXmlAsync(path));
|
||||
public async Task<ImmutableArray<(ContentPath, Result<byte[]>)>> LoadPackageBinaryFilesAsync(ImmutableArray<ContentPath> filePaths)
|
||||
=> await LoadPackageDataFilesAsync(filePaths, async path => await TryLoadBinaryAsync(path));
|
||||
public async Task<ImmutableArray<(ContentPath, Result<string>)>> LoadPackageTextFilesAsync(ImmutableArray<ContentPath> filePaths)
|
||||
=> await LoadPackageDataFilesAsync(filePaths, async path => await TryLoadTextAsync(path));
|
||||
|
||||
|
||||
private int _useCaching;
|
||||
public bool UseCaching
|
||||
{
|
||||
@@ -98,156 +294,14 @@ public class StorageService : IStorageService
|
||||
set => ModUtils.Threading.SetBool(ref _useCaching, value);
|
||||
}
|
||||
|
||||
public virtual FluentResults.Result<XDocument> LoadLocalXml(ContentPackage package, string localFilePath) =>
|
||||
GetAbsoluePathFromLocal(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null }
|
||||
? TryLoadXml(r.Value) : r.ToResult();
|
||||
public virtual FluentResults.Result<byte[]> LoadLocalBinary(ContentPackage package, string localFilePath) =>
|
||||
GetAbsoluePathFromLocal(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null }
|
||||
? TryLoadBinary(r.Value) : r.ToResult();
|
||||
public virtual FluentResults.Result<string> LoadLocalText(ContentPackage package, string localFilePath) =>
|
||||
GetAbsoluePathFromLocal(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null }
|
||||
? TryLoadText(r.Value) : r.ToResult();
|
||||
public virtual FluentResults.Result SaveLocalXml(ContentPackage package, string localFilePath, XDocument document) =>
|
||||
GetAbsoluePathFromLocal(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null }
|
||||
? TrySaveXml(r.Value, document) : r.ToResult();
|
||||
public virtual FluentResults.Result SaveLocalBinary(ContentPackage package, string localFilePath, in byte[] bytes) =>
|
||||
GetAbsoluePathFromLocal(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null }
|
||||
? TrySaveBinary(r.Value, bytes) : r.ToResult();
|
||||
public virtual FluentResults.Result SaveLocalText(ContentPackage package, string localFilePath, in string text) =>
|
||||
GetAbsoluePathFromLocal(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null }
|
||||
? TrySaveText(r.Value, text) : r.ToResult();
|
||||
public virtual async Task<FluentResults.Result<XDocument>> LoadLocalXmlAsync(ContentPackage package, string localFilePath) =>
|
||||
GetAbsoluePathFromLocal(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null }
|
||||
? await TryLoadXmlAsync(r.Value) : r.ToResult();
|
||||
public virtual async Task<FluentResults.Result<byte[]>> LoadLocalBinaryAsync(ContentPackage package, string localFilePath) =>
|
||||
GetAbsoluePathFromLocal(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null }
|
||||
? await TryLoadBinaryAsync(r.Value) : r.ToResult();
|
||||
public virtual async Task<FluentResults.Result<string>> LoadLocalTextAsync(ContentPackage package, string localFilePath) =>
|
||||
GetAbsoluePathFromLocal(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null }
|
||||
? await TryLoadTextAsync(r.Value) : r.ToResult();
|
||||
public virtual async Task<FluentResults.Result> SaveLocalXmlAsync(ContentPackage package, string localFilePath, XDocument document) =>
|
||||
GetAbsoluePathFromLocal(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null }
|
||||
? await TrySaveXmlAsync(r.Value, document) : r.ToResult();
|
||||
public virtual async Task<FluentResults.Result> SaveLocalBinaryAsync(ContentPackage package, string localFilePath, byte[] bytes) =>
|
||||
GetAbsoluePathFromLocal(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null }
|
||||
? await TrySaveBinaryAsync(r.Value, bytes) : r.ToResult();
|
||||
public virtual async Task<FluentResults.Result> SaveLocalTextAsync(ContentPackage package, string localFilePath, string text) =>
|
||||
GetAbsoluePathFromLocal(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null }
|
||||
? await TrySaveTextAsync(r.Value, text) : r.ToResult();
|
||||
public virtual FluentResults.Result<XDocument> LoadPackageXml(ContentPackage package, string localFilePath) =>
|
||||
GetAbsoluePathFromPackage(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null }
|
||||
? TryLoadXml(r.Value) : r.ToResult();
|
||||
public virtual FluentResults.Result<byte[]> LoadPackageBinary(ContentPackage package, string localFilePath) =>
|
||||
GetAbsoluePathFromPackage(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null }
|
||||
? TryLoadBinary(r.Value) : r.ToResult();
|
||||
public virtual FluentResults.Result<string> LoadPackageText(ContentPackage package, string localFilePath) =>
|
||||
GetAbsoluePathFromPackage(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null }
|
||||
? TryLoadText(r.Value) : r.ToResult();
|
||||
// Method group redirect
|
||||
private FluentResults.Result<XDocument> TryLoadXml(string filePath) => TryLoadXml(filePath, null);
|
||||
|
||||
|
||||
|
||||
public virtual ImmutableArray<(string, FluentResults.Result<XDocument>)> LoadPackageXmlFiles(ContentPackage package, ImmutableArray<string> localFilePaths)
|
||||
public virtual FluentResults.Result<XDocument> TryLoadXml(string filePath, Encoding encoding)
|
||||
{
|
||||
((IService)this).CheckDisposed();
|
||||
if (localFilePaths.IsDefaultOrEmpty)
|
||||
return ImmutableArray<(string, FluentResults.Result<XDocument>)>.Empty;
|
||||
var builder = ImmutableArray.CreateBuilder<(string, FluentResults.Result<XDocument>)>(localFilePaths.Length);
|
||||
foreach (var path in localFilePaths)
|
||||
builder.Add((path, LoadPackageXml(package, path)));
|
||||
return builder.MoveToImmutable();
|
||||
}
|
||||
|
||||
public virtual ImmutableArray<(string, FluentResults.Result<byte[]>)> LoadPackageBinaryFiles(ContentPackage package, ImmutableArray<string> localFilePaths)
|
||||
{
|
||||
((IService)this).CheckDisposed();
|
||||
if (localFilePaths.IsDefaultOrEmpty)
|
||||
return ImmutableArray<(string, FluentResults.Result<byte[]>)>.Empty;
|
||||
var builder = ImmutableArray.CreateBuilder<(string, FluentResults.Result<byte[]>)>(localFilePaths.Length);
|
||||
foreach (var path in localFilePaths)
|
||||
builder.Add((path, LoadPackageBinary(package, path)));
|
||||
return builder.MoveToImmutable();
|
||||
}
|
||||
|
||||
public virtual ImmutableArray<(string, FluentResults.Result<string>)> LoadPackageTextFiles(ContentPackage package, ImmutableArray<string> localFilePaths)
|
||||
{
|
||||
((IService)this).CheckDisposed();
|
||||
if (localFilePaths.IsDefaultOrEmpty)
|
||||
return ImmutableArray<(string, FluentResults.Result<string>)>.Empty;
|
||||
var builder = ImmutableArray.CreateBuilder<(string, FluentResults.Result<string>)>(localFilePaths.Length);
|
||||
foreach (var path in localFilePaths)
|
||||
builder.Add((path, LoadPackageText(package, path)));
|
||||
return builder.MoveToImmutable();
|
||||
}
|
||||
|
||||
public virtual FluentResults.Result<ImmutableArray<string>> FindFilesInPackage(ContentPackage package, string localSubfolder, string regexFilter, bool searchRecursively)
|
||||
{
|
||||
((IService)this).CheckDisposed();
|
||||
var r = GetAbsoluePathFromPackage(package, localSubfolder);
|
||||
if (r is { IsFailed: true })
|
||||
return r.ToResult();
|
||||
var builder = ImmutableArray.CreateBuilder<(string, FluentResults.Result<ImmutableArray<string>>)>();
|
||||
var sOption = searchRecursively ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
|
||||
string[] arr = Directory.GetFiles(localSubfolder, regexFilter.IsNullOrWhiteSpace() ? "*.*" : regexFilter, sOption);
|
||||
return new FluentResults.Result<ImmutableArray<string>>().WithSuccess($"Files found.")
|
||||
.WithValue(arr.ToImmutableArray());
|
||||
}
|
||||
|
||||
public virtual async Task<FluentResults.Result<XDocument>> LoadPackageXmlAsync(ContentPackage package, string localFilePath) =>
|
||||
GetAbsoluePathFromPackage(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null }
|
||||
? await TryLoadXmlAsync(r.Value) : r.ToResult();
|
||||
|
||||
public virtual async Task<FluentResults.Result<byte[]>> LoadPackageBinaryAsync(ContentPackage package, string localFilePath) =>
|
||||
GetAbsoluePathFromPackage(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null }
|
||||
? await TryLoadBinaryAsync(r.Value) : r.ToResult();
|
||||
|
||||
public virtual async Task<FluentResults.Result<string>> LoadPackageTextAsync(ContentPackage package, string localFilePath) =>
|
||||
GetAbsoluePathFromPackage(package, localFilePath) is var r && r is { IsSuccess: true, Value: not null }
|
||||
? await TryLoadTextAsync(r.Value) : r.ToResult();
|
||||
|
||||
public virtual async Task<ImmutableArray<(string, FluentResults.Result<XDocument>)>> LoadPackageXmlFilesAsync(ContentPackage package, ImmutableArray<string> localFilePaths)
|
||||
{
|
||||
((IService)this).CheckDisposed();
|
||||
if (localFilePaths.IsDefaultOrEmpty)
|
||||
return ImmutableArray<(string, FluentResults.Result<XDocument>)>.Empty;
|
||||
var builder = ImmutableArray.CreateBuilder<(string, FluentResults.Result<XDocument>)>(localFilePaths.Length);
|
||||
using var lck = OperationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
IService.CheckDisposed(this);
|
||||
|
||||
await localFilePaths.ParallelForEachAsync(async path =>
|
||||
{
|
||||
builder.Add((path, await LoadPackageXmlAsync(package, path)));
|
||||
}, maxDegreeOfParallelism: 2);
|
||||
return builder.MoveToImmutable();
|
||||
}
|
||||
|
||||
public virtual async Task<ImmutableArray<(string, FluentResults.Result<byte[]>)>> LoadPackageBinaryFilesAsync(ContentPackage package, ImmutableArray<string> localFilePaths)
|
||||
{
|
||||
((IService)this).CheckDisposed();
|
||||
if (localFilePaths.IsDefaultOrEmpty)
|
||||
return ImmutableArray<(string, FluentResults.Result<byte[]>)>.Empty;
|
||||
var builder = ImmutableArray.CreateBuilder<(string, FluentResults.Result<byte[]>)>(localFilePaths.Length);
|
||||
await localFilePaths.ParallelForEachAsync(async path =>
|
||||
{
|
||||
builder.Add((path, await LoadPackageBinaryAsync(package, path)));
|
||||
}, maxDegreeOfParallelism: 2);
|
||||
return builder.MoveToImmutable();
|
||||
}
|
||||
|
||||
public virtual async Task<ImmutableArray<(string, FluentResults.Result<string>)>> LoadPackageTextFilesAsync(ContentPackage package, ImmutableArray<string> localFilePaths)
|
||||
{
|
||||
((IService)this).CheckDisposed();
|
||||
if (localFilePaths.IsDefaultOrEmpty)
|
||||
return ImmutableArray<(string, FluentResults.Result<string>)>.Empty;
|
||||
var builder = ImmutableArray.CreateBuilder<(string, FluentResults.Result<string>)>(localFilePaths.Length);
|
||||
await localFilePaths.ParallelForEachAsync(async path =>
|
||||
{
|
||||
builder.Add((path, await LoadPackageTextAsync(package, path)));
|
||||
}, maxDegreeOfParallelism: 2);
|
||||
return builder.MoveToImmutable();
|
||||
}
|
||||
|
||||
|
||||
public virtual FluentResults.Result<XDocument> TryLoadXml(string filePath, Encoding encoding = null)
|
||||
{
|
||||
((IService)this).CheckDisposed();
|
||||
var r = TryLoadText(filePath, encoding);
|
||||
if (r is { IsSuccess: true, Value: not null })
|
||||
return XDocument.Parse(r.Value);
|
||||
@@ -258,9 +312,13 @@ public class StorageService : IStorageService
|
||||
}
|
||||
}
|
||||
|
||||
public virtual FluentResults.Result<string> TryLoadText(string filePath, Encoding encoding = null)
|
||||
// Method group redirect
|
||||
private FluentResults.Result<string> TryLoadText(string filePath) => TryLoadText(filePath, null);
|
||||
public virtual FluentResults.Result<string> TryLoadText(string filePath, Encoding encoding)
|
||||
{
|
||||
((IService)this).CheckDisposed();
|
||||
using var lck = OperationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
IService.CheckDisposed(this);
|
||||
|
||||
if (UseCaching && _fsCache.TryGetValue(filePath, out var result)
|
||||
&& result.TryPickT1(out var cachedVal, out _))
|
||||
{
|
||||
@@ -280,7 +338,9 @@ public class StorageService : IStorageService
|
||||
|
||||
public virtual FluentResults.Result<byte[]> TryLoadBinary(string filePath)
|
||||
{
|
||||
((IService)this).CheckDisposed();
|
||||
using var lck = OperationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
IService.CheckDisposed(this);
|
||||
|
||||
if (UseCaching && _fsCache.TryGetValue(filePath, out var result)
|
||||
&& result.TryPickT0(out var cachedVal, out _))
|
||||
{
|
||||
@@ -301,14 +361,10 @@ public class StorageService : IStorageService
|
||||
public virtual FluentResults.Result TrySaveXml(string filePath, in XDocument document, Encoding encoding = null) => TrySaveText(filePath, document.ToString(), encoding);
|
||||
public virtual FluentResults.Result TrySaveText(string filePath, in string text, Encoding encoding = null)
|
||||
{
|
||||
((IService)this).CheckDisposed();
|
||||
if (text.IsNullOrWhiteSpace())
|
||||
{
|
||||
return FluentResults.Result.Fail($"Contents are empty for {filePath}")
|
||||
.WithError(new Error($"Contents are empty for {filePath}")
|
||||
.WithMetadata(MetadataType.ExceptionObject, this)
|
||||
.WithMetadata(MetadataType.Sources, filePath));
|
||||
}
|
||||
Guard.IsNotNullOrWhiteSpace(text, nameof(text));
|
||||
using var lck = OperationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
IService.CheckDisposed(this);
|
||||
|
||||
string t = text; //copy
|
||||
return IOExceptionsOperationRunner(nameof(TrySaveText), filePath, () =>
|
||||
{
|
||||
@@ -321,16 +377,15 @@ public class StorageService : IStorageService
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public virtual FluentResults.Result TrySaveBinary(string filePath, in byte[] bytes)
|
||||
{
|
||||
((IService)this).CheckDisposed();
|
||||
if (bytes is null || bytes.Length == 0)
|
||||
{
|
||||
return FluentResults.Result.Fail($"Byte array is null or empty for {filePath}")
|
||||
.WithError(new Error($"Byte array is null or empty for {filePath}")
|
||||
.WithMetadata(MetadataType.ExceptionObject, this)
|
||||
.WithMetadata(MetadataType.Sources, filePath));
|
||||
}
|
||||
Guard.IsNotNull(bytes, nameof(bytes));
|
||||
Guard.HasSizeGreaterThanOrEqualTo(bytes, 1, nameof(bytes));
|
||||
using var lck = OperationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
IService.CheckDisposed(this);
|
||||
|
||||
|
||||
byte[] b = new byte[bytes.Length];
|
||||
System.Buffer.BlockCopy(bytes, 0, b, 0, bytes.Length);
|
||||
return IOExceptionsOperationRunner(nameof(TrySaveBinary), filePath, () =>
|
||||
@@ -346,7 +401,7 @@ public class StorageService : IStorageService
|
||||
|
||||
public virtual FluentResults.Result<bool> FileExists(string filePath)
|
||||
{
|
||||
((IService)this).CheckDisposed();
|
||||
IService.CheckDisposed(this);
|
||||
return IOExceptionsOperationRunner<bool>(nameof(FileExists), filePath, () =>
|
||||
{
|
||||
var fp = filePath.CleanUpPath();
|
||||
@@ -357,7 +412,7 @@ public class StorageService : IStorageService
|
||||
|
||||
public virtual FluentResults.Result<bool> DirectoryExists(string directoryPath)
|
||||
{
|
||||
((IService)this).CheckDisposed();
|
||||
IService.CheckDisposed(this);
|
||||
try
|
||||
{
|
||||
var di = new DirectoryInfo(directoryPath);
|
||||
@@ -371,7 +426,7 @@ public class StorageService : IStorageService
|
||||
|
||||
public virtual async Task<FluentResults.Result<XDocument>> TryLoadXmlAsync(string filePath, Encoding encoding = null)
|
||||
{
|
||||
((IService)this).CheckDisposed();
|
||||
IService.CheckDisposed(this);
|
||||
if (UseCaching && _fsCache.TryGetValue(filePath, out var cachedVal)
|
||||
&& cachedVal.TryPickT2(out var cachedDoc, out _))
|
||||
return FluentResults.Result.Ok(cachedDoc);
|
||||
@@ -391,7 +446,7 @@ public class StorageService : IStorageService
|
||||
|
||||
public virtual async Task<FluentResults.Result<string>> TryLoadTextAsync(string filePath, Encoding encoding = null)
|
||||
{
|
||||
((IService)this).CheckDisposed();
|
||||
IService.CheckDisposed(this);
|
||||
if (UseCaching && _fsCache.TryGetValue(filePath, out var cachedVal)
|
||||
&& cachedVal.TryPickT1(out var cachedTxt, out _))
|
||||
return FluentResults.Result.Ok(cachedTxt);
|
||||
@@ -409,7 +464,7 @@ public class StorageService : IStorageService
|
||||
|
||||
public virtual async Task<FluentResults.Result<byte[]>> TryLoadBinaryAsync(string filePath)
|
||||
{
|
||||
((IService)this).CheckDisposed();
|
||||
IService.CheckDisposed(this);
|
||||
if (UseCaching && _fsCache.TryGetValue(filePath, out var cachedVal)
|
||||
&& cachedVal.TryPickT0(out var cachedBin, out _))
|
||||
{
|
||||
@@ -427,14 +482,9 @@ public class StorageService : IStorageService
|
||||
public virtual async Task<FluentResults.Result> TrySaveXmlAsync(string filePath, XDocument document, Encoding encoding = null) => await TrySaveTextAsync(filePath, document.ToString(), encoding);
|
||||
public virtual async Task<FluentResults.Result> TrySaveTextAsync(string filePath, string text, Encoding encoding = null)
|
||||
{
|
||||
((IService)this).CheckDisposed();
|
||||
if (text.IsNullOrWhiteSpace())
|
||||
{
|
||||
return FluentResults.Result.Fail($"Contents are empty for {filePath}")
|
||||
.WithError(new Error($"Contents are empty for {filePath}")
|
||||
.WithMetadata(MetadataType.ExceptionObject, this)
|
||||
.WithMetadata(MetadataType.Sources, filePath));
|
||||
}
|
||||
Guard.IsNotNullOrWhiteSpace(text, nameof(text));
|
||||
using var lck = await OperationsLock.AcquireReaderLock();
|
||||
IService.CheckDisposed(this);
|
||||
|
||||
string t = text.ToString(); //copy
|
||||
return await IOExceptionsOperationRunnerAsync(nameof(TrySaveText), filePath, async () =>
|
||||
@@ -450,14 +500,12 @@ public class StorageService : IStorageService
|
||||
|
||||
public virtual async Task<FluentResults.Result> TrySaveBinaryAsync(string filePath, byte[] bytes)
|
||||
{
|
||||
((IService)this).CheckDisposed();
|
||||
if (bytes is null || bytes.Length == 0)
|
||||
{
|
||||
return FluentResults.Result.Fail($"Byte array is null or empty for {filePath}")
|
||||
.WithError(new Error($"Byte array is null or empty for {filePath}")
|
||||
.WithMetadata(MetadataType.ExceptionObject, this)
|
||||
.WithMetadata(MetadataType.Sources, filePath));
|
||||
}
|
||||
Guard.IsNotNull(bytes, nameof(bytes));
|
||||
Guard.HasSizeGreaterThanOrEqualTo(bytes, 1, nameof(bytes));
|
||||
using var lck = await OperationsLock.AcquireReaderLock();
|
||||
IService.CheckDisposed(this);
|
||||
|
||||
|
||||
byte[] b = new byte[bytes.Length];
|
||||
System.Buffer.BlockCopy(bytes, 0, b, 0, bytes.Length);
|
||||
return await IOExceptionsOperationRunnerAsync(nameof(TrySaveBinary), filePath, async () =>
|
||||
@@ -479,6 +527,8 @@ public class StorageService : IStorageService
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (e is ArgumentException or ArgumentNullException)
|
||||
throw;
|
||||
return ReturnException(e, filepath).WithError(GetGeneralError(funcName, filepath));
|
||||
}
|
||||
}
|
||||
@@ -491,6 +541,8 @@ public class StorageService : IStorageService
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (e is ArgumentException or ArgumentNullException)
|
||||
throw;
|
||||
return ReturnException(e, filepath).WithError(GetGeneralError(funcName, filepath));
|
||||
}
|
||||
}
|
||||
@@ -503,6 +555,8 @@ public class StorageService : IStorageService
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (e is ArgumentException or ArgumentNullException)
|
||||
throw;
|
||||
return ReturnException(e, filepath).WithError(GetGeneralError(funcName, filepath));
|
||||
}
|
||||
}
|
||||
@@ -515,6 +569,8 @@ public class StorageService : IStorageService
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (e is ArgumentException or ArgumentNullException)
|
||||
throw;
|
||||
return ReturnException(e, filepath).WithError(GetGeneralError(funcName, filepath));
|
||||
}
|
||||
}
|
||||
@@ -530,50 +586,6 @@ public class StorageService : IStorageService
|
||||
.WithMetadata(MetadataType.ExceptionObject, this)
|
||||
.WithMetadata(MetadataType.Sources, localfp);
|
||||
|
||||
private FluentResults.Result<string> GetAbsoluePathFromLocal(ContentPackage package, string localFilePath)
|
||||
{
|
||||
if (Path.IsPathRooted(localFilePath))
|
||||
{
|
||||
return new FluentResults.Result<string>().WithError(
|
||||
new Error($"The path '{localFilePath}' is a rooted path. Must be relative!")
|
||||
.WithMetadata(MetadataType.ExceptionObject, this)
|
||||
.WithMetadata(MetadataType.RootObject, localFilePath));
|
||||
}
|
||||
|
||||
Guard.IsNotNull(package, nameof(package));
|
||||
|
||||
return new FluentResults.Result<string>().WithSuccess($"Path constructed")
|
||||
.WithValue(System.IO.Path.GetFullPath(System.IO.Path.Combine(
|
||||
_configData.RunLocation,
|
||||
_configData.LocalPackageDataPath.Replace(
|
||||
_configData.LocalDataPathRegex,
|
||||
package.TryExtractSteamWorkshopId(out var id) ? id.Value.ToString() : package.Name),
|
||||
localFilePath)));
|
||||
}
|
||||
|
||||
public FluentResults.Result<string> GetAbsoluePathFromPackage(ContentPackage package, string localFilePath)
|
||||
{
|
||||
Guard.IsNotNull(package, nameof(package));
|
||||
|
||||
if (localFilePath.IsNullOrWhiteSpace())
|
||||
{
|
||||
return new FluentResults.Result<string>().WithValue(Path.GetFullPath(package.Path.CleanUpPath()));
|
||||
}
|
||||
|
||||
var path = localFilePath.CleanUpPath();
|
||||
|
||||
if (Path.IsPathRooted(path))
|
||||
{
|
||||
return new FluentResults.Result<string>().WithError(
|
||||
new Error($"The path '{localFilePath}' is a rooted path. Must be relative!")
|
||||
.WithMetadata(MetadataType.ExceptionObject, this)
|
||||
.WithMetadata(MetadataType.RootObject, localFilePath));
|
||||
}
|
||||
|
||||
return new FluentResults.Result<string>().WithSuccess($"Path constructed")
|
||||
.WithValue(Path.Combine(Path.GetFullPath(package.Path.CleanUpPath()), path));
|
||||
}
|
||||
|
||||
private FluentResults.Result<TReturn> ReturnException<TReturn, TException>(TException exception, ContentPackage package) where TException : Exception
|
||||
{
|
||||
return new FluentResults.Result<TReturn>().WithError(new ExceptionalError(exception)
|
||||
|
||||
@@ -27,7 +27,7 @@ public partial interface IConfigService : IReusableService, ILuaConfigService
|
||||
|
||||
// Config Files/Resources
|
||||
Task<FluentResults.Result> LoadConfigsAsync(ImmutableArray<IConfigResourceInfo> configResources);
|
||||
Task<FluentResults.Result> LoadConfigsProfilesAsync(ImmutableArray<IConfigProfileResourceInfo> configProfileResources);
|
||||
Task<FluentResults.Result> LoadConfigsProfilesAsync(ImmutableArray<IConfigResourceInfo> configProfileResources);
|
||||
|
||||
// Immediate Mode
|
||||
FluentResults.Result<TConfig> AddConfig<TConfig>(IConfigInfo configInfo) where TConfig : IConfigBase;
|
||||
|
||||
@@ -9,7 +9,7 @@ using Barotrauma.LuaCs.Data;
|
||||
|
||||
namespace Barotrauma.LuaCs.Services;
|
||||
|
||||
public interface IPackageManagementService : IReusableService, IConfigsResourcesInfo, IConfigProfilesResourcesInfo, ILuaScriptsResourcesInfo, IAssembliesResourcesInfo
|
||||
public interface IPackageManagementService : IReusableService, IConfigsResourcesInfo, ILuaScriptsResourcesInfo, IAssembliesResourcesInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Loads and parses the provided <see cref="ContentPackage"/> for <see cref="IResourceInfo"/> supported by the current runtime environment.
|
||||
@@ -46,16 +46,13 @@ public interface IPackageManagementService : IReusableService, IConfigsResources
|
||||
// single
|
||||
FluentResults.Result<IAssembliesResourcesInfo> GetAssembliesInfos(ContentPackage package, bool onlySupportedResources = true);
|
||||
FluentResults.Result<IConfigsResourcesInfo> GetConfigsInfos(ContentPackage package, bool onlySupportedResources = true);
|
||||
FluentResults.Result<IConfigProfilesResourcesInfo> GetConfigProfilesInfos(ContentPackage package, bool onlySupportedResources = true);
|
||||
FluentResults.Result<ILuaScriptsResourcesInfo> GetLuaScriptsInfos(ContentPackage package, bool onlySupportedResources = true);
|
||||
// collection
|
||||
FluentResults.Result<IAssembliesResourcesInfo> GetAssembliesInfos(IReadOnlyList<ContentPackage> packages, bool onlySupportedResources = true);
|
||||
FluentResults.Result<IConfigsResourcesInfo> GetConfigsInfos(IReadOnlyList<ContentPackage> packages, bool onlySupportedResources = true);
|
||||
FluentResults.Result<IConfigProfilesResourcesInfo> GetConfigProfilesInfos(IReadOnlyList<ContentPackage> packages, bool onlySupportedResources = true);
|
||||
FluentResults.Result<ILuaScriptsResourcesInfo> GetLuaScriptsInfos(IReadOnlyList<ContentPackage> packages, bool onlySupportedResources = true);
|
||||
|
||||
Task<FluentResults.Result<IAssembliesResourcesInfo>> GetAssembliesInfosAsync(IReadOnlyList<ContentPackage> packages, bool onlySupportedResources = true);
|
||||
Task<FluentResults.Result<IConfigsResourcesInfo>> GetConfigsInfosAsync(IReadOnlyList<ContentPackage> packages, bool onlySupportedResources = true);
|
||||
Task<FluentResults.Result<IConfigProfilesResourcesInfo>> GetConfigProfilesInfosAsync(IReadOnlyList<ContentPackage> packages, bool onlySupportedResources = true);
|
||||
Task<FluentResults.Result<ILuaScriptsResourcesInfo>> GetLuaScriptsInfosAsync(IReadOnlyList<ContentPackage> packages, bool onlySupportedResources = true);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using Microsoft.Toolkit.Diagnostics;
|
||||
|
||||
namespace Barotrauma.LuaCs.Services;
|
||||
|
||||
@@ -25,6 +26,12 @@ public interface IService : IDisposable
|
||||
public void CheckDisposed()
|
||||
{
|
||||
if (IsDisposed)
|
||||
throw new ObjectDisposedException($"Tried to call method on disposed object '{this.GetType().Name}'!");
|
||||
ThrowHelper.ThrowObjectDisposedException($"Tried to call method on disposed object '{this.GetType().Name}'!");
|
||||
}
|
||||
|
||||
static void CheckDisposed(IService service)
|
||||
{
|
||||
if (service.IsDisposed)
|
||||
ThrowHelper.ThrowObjectDisposedException($"Tried to call method on disposed object '{service.GetType().Name}'!");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Collections.Immutable;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml.Linq;
|
||||
using FluentResults;
|
||||
|
||||
namespace Barotrauma.LuaCs.Services;
|
||||
|
||||
@@ -28,7 +29,7 @@ public interface IStorageService : IService
|
||||
/// <param name="absolutePaths"></param>
|
||||
void PurgeFilesFromCache(params string[] absolutePaths);
|
||||
|
||||
// -- local game folder storage
|
||||
// -- local game folder storage
|
||||
FluentResults.Result<XDocument> LoadLocalXml(ContentPackage package, string localFilePath);
|
||||
FluentResults.Result<byte[]> LoadLocalBinary(ContentPackage package, string localFilePath);
|
||||
FluentResults.Result<string> LoadLocalText(ContentPackage package, string localFilePath);
|
||||
@@ -45,24 +46,23 @@ public interface IStorageService : IService
|
||||
|
||||
// -- package directory
|
||||
// singles
|
||||
FluentResults.Result<XDocument> LoadPackageXml(ContentPackage package, string localFilePath);
|
||||
FluentResults.Result<byte[]> LoadPackageBinary(ContentPackage package, string localFilePath);
|
||||
FluentResults.Result<string> LoadPackageText(ContentPackage package, string localFilePath);
|
||||
Result<XDocument> LoadPackageXml(ContentPath filePath);
|
||||
Result<byte[]> LoadPackageBinary(ContentPath filePath);
|
||||
Result<string> LoadPackageText(ContentPath filePath);
|
||||
// collections
|
||||
ImmutableArray<(string, FluentResults.Result<XDocument>)> LoadPackageXmlFiles(ContentPackage package, ImmutableArray<string> localFilePaths);
|
||||
ImmutableArray<(string, FluentResults.Result<byte[]>)> LoadPackageBinaryFiles(ContentPackage package, ImmutableArray<string> localFilePaths);
|
||||
ImmutableArray<(string, FluentResults.Result<string>)> LoadPackageTextFiles(ContentPackage package, ImmutableArray<string> localFilePaths);
|
||||
ImmutableArray<(ContentPath, Result<XDocument>)> LoadPackageXmlFiles(ImmutableArray<ContentPath> filePaths);
|
||||
ImmutableArray<(ContentPath, Result<byte[]>)> LoadPackageBinaryFiles(ImmutableArray<ContentPath> filePaths);
|
||||
ImmutableArray<(ContentPath, Result<string>)> LoadPackageTextFiles(ImmutableArray<ContentPath> filePaths);
|
||||
FluentResults.Result<ImmutableArray<string>> FindFilesInPackage(ContentPackage package, string localSubfolder, string regexFilter, bool searchRecursively);
|
||||
FluentResults.Result<string> GetAbsoluePathFromPackage(ContentPackage package, string localFilePath);
|
||||
// async
|
||||
// singles
|
||||
Task<FluentResults.Result<XDocument>> LoadPackageXmlAsync(ContentPackage package, string localFilePath);
|
||||
Task<FluentResults.Result<byte[]>> LoadPackageBinaryAsync(ContentPackage package, string localFilePath);
|
||||
Task<FluentResults.Result<string>> LoadPackageTextAsync(ContentPackage package, string localFilePath);
|
||||
Task<Result<XDocument>> LoadPackageXmlAsync(ContentPath filePath);
|
||||
Task<Result<byte[]>> LoadPackageBinaryAsync(ContentPath filePath);
|
||||
Task<Result<string>> LoadPackageTextAsync(ContentPath filePath);
|
||||
// collections
|
||||
Task<ImmutableArray<(string, FluentResults.Result<XDocument>)>> LoadPackageXmlFilesAsync(ContentPackage package, ImmutableArray<string> localFilePaths);
|
||||
Task<ImmutableArray<(string, FluentResults.Result<byte[]>)>> LoadPackageBinaryFilesAsync(ContentPackage package, ImmutableArray<string> localFilePaths);
|
||||
Task<ImmutableArray<(string, FluentResults.Result<string>)>> LoadPackageTextFilesAsync(ContentPackage package, ImmutableArray<string> localFilePaths);
|
||||
Task<ImmutableArray<(ContentPath, Result<XDocument>)>> LoadPackageXmlFilesAsync(ImmutableArray<ContentPath> filePaths);
|
||||
Task<ImmutableArray<(ContentPath, Result<byte[]>)>> LoadPackageBinaryFilesAsync(ImmutableArray<ContentPath> filePaths);
|
||||
Task<ImmutableArray<(ContentPath, Result<string>)>> LoadPackageTextFilesAsync(ImmutableArray<ContentPath> filePaths);
|
||||
|
||||
// -- absolute paths
|
||||
FluentResults.Result<XDocument> TryLoadXml(string filePath, Encoding encoding = null);
|
||||
|
||||
Reference in New Issue
Block a user