- SafeStorageService glow up.
- ILuaScriptLoader now inherits the ISafeStorageValidation interface. - LuaScriptLoader now uses the SafeStorageService.
This commit is contained in:
@@ -6,7 +6,7 @@ using MoonSharp.Interpreter.Loaders;
|
|||||||
|
|
||||||
namespace Barotrauma.LuaCs.Services.Safe;
|
namespace Barotrauma.LuaCs.Services.Safe;
|
||||||
|
|
||||||
public interface ILuaScriptLoader : IService, IScriptLoader
|
public interface ILuaScriptLoader : IService, IScriptLoader, ISafeStorageValidation
|
||||||
{
|
{
|
||||||
void ClearCaches();
|
void ClearCaches();
|
||||||
Task<Result<ImmutableArray<(ContentPath Path, Result<string>)>>> CacheResourcesAsync(ImmutableArray<ILuaScriptResourceInfo> resourceInfos);
|
Task<Result<ImmutableArray<(ContentPath Path, Result<string>)>>> CacheResourcesAsync(ImmutableArray<ILuaScriptResourceInfo> resourceInfos);
|
||||||
|
|||||||
+17
-8
@@ -2,7 +2,9 @@
|
|||||||
|
|
||||||
namespace Barotrauma.LuaCs.Services.Safe;
|
namespace Barotrauma.LuaCs.Services.Safe;
|
||||||
|
|
||||||
public interface ISafeStorageService : IStorageService
|
public interface ISafeStorageService : IStorageService, ISafeStorageValidation { }
|
||||||
|
|
||||||
|
public interface ISafeStorageValidation
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Checks the given file path to see if it can be read. This includes any permissions, whitelists and OS checks.
|
/// Checks the given file path to see if it can be read. This includes any permissions, whitelists and OS checks.
|
||||||
@@ -16,26 +18,33 @@ public interface ISafeStorageService : IStorageService
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds the given path to the specified whitelists.
|
/// Adds the given path to the specified whitelists.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="path">Either the fully-qualified or local reference path to the given file.</param>
|
/// <param name="path">The path to the file, exactly as it will be passed to the Try(Load|Save) methods in <see cref="StorageService"/>.</param>
|
||||||
/// <param name="readOnly"></param>
|
/// <param name="readOnly">Whether to add it to the read whitelist only, or Read+Write whitelists.</param>
|
||||||
void AddFileToWhitelist(string path, bool readOnly = true);
|
void AddFileToWhitelist(string path, bool readOnly = true);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Removes the given path from all whitelists (Read|Write).
|
/// Adds the given collection of file paths to whitelists (Read|+Write)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="paths">The paths to the files, formatted exactly as it will be passed to the Try(Load|Save) methods in <see cref="StorageService"/>.</param>
|
||||||
|
/// <param name="readOnly">Whether to add it to the read whitelist only, or Read+Write whitelists.</param>
|
||||||
|
void AddFilesToWhitelist(ImmutableArray<string> paths, bool readOnly = true);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes the given path from all whitelists (Read|+Write).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="path"></param>
|
/// <param name="path"></param>
|
||||||
void RemoveFileFromAllWhitelists(string path);
|
void RemoveFileFromAllWhitelists(string path);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets the whitelist filtering for read-only file permissions for the instance.
|
/// Sets the whitelist filtering for read-only file permissions for the instance. Overwrites previous list.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="filePaths">List of absolute file paths allowed.</param>
|
/// <param name="filePaths">List of file paths allowed, as will be passed to the <see cref="StorageService"/> Try(Load|Save) methods.</param>
|
||||||
FluentResults.Result SetReadOnlyWhitelist(ImmutableArray<string> filePaths);
|
FluentResults.Result SetReadOnlyWhitelist(ImmutableArray<string> filePaths);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets the whitelist filtering for read & write file permissions for the instance.
|
/// Sets the whitelist filtering for read & write file permissions for the instance. Overwrites previous lists.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="filePaths">List of absolute file paths allowed.</param>
|
/// <param name="filePaths">List of file paths allowed, as will be passed to the <see cref="StorageService"/> Try(Load|Save) methods.</param>
|
||||||
FluentResults.Result SetReadWriteWhitelist(ImmutableArray<string> filePaths);
|
FluentResults.Result SetReadWriteWhitelist(ImmutableArray<string> filePaths);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -15,99 +15,77 @@ namespace Barotrauma.LuaCs.Services.Safe
|
|||||||
{
|
{
|
||||||
public class LuaScriptLoader : ScriptLoaderBase, ILuaScriptLoader
|
public class LuaScriptLoader : ScriptLoaderBase, ILuaScriptLoader
|
||||||
{
|
{
|
||||||
public LuaScriptLoader(IStorageService storageService, Lazy<ILoggerService> loggerService, ILuaScriptServicesConfig luaScriptServicesConfig)
|
public LuaScriptLoader(ISafeStorageService storageService, Lazy<ILoggerService> loggerService)
|
||||||
{
|
{
|
||||||
this._storageService = storageService;
|
this._storageService = storageService;
|
||||||
this._loggerService = loggerService;
|
this._loggerService = loggerService;
|
||||||
this._luaScriptServicesConfig = luaScriptServicesConfig;
|
|
||||||
_storageService.UseCaching = _luaScriptServicesConfig.UseCaching;
|
|
||||||
if (_luaScriptServicesConfig.SafeLuaIOEnabled)
|
|
||||||
{
|
|
||||||
//_storageService.EnableWhitelistOnly();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly IStorageService _storageService;
|
private readonly ISafeStorageService _storageService;
|
||||||
private readonly Lazy<ILoggerService> _loggerService;
|
private readonly Lazy<ILoggerService> _loggerService;
|
||||||
private readonly ILuaScriptServicesConfig _luaScriptServicesConfig;
|
|
||||||
|
|
||||||
public override object LoadFile(string file, Table globalContext)
|
public override object LoadFile(string file, Table globalContext)
|
||||||
{
|
{
|
||||||
((IService)this).CheckDisposed();
|
IService.CheckDisposed(this);
|
||||||
|
if (file.IsNullOrWhiteSpace())
|
||||||
if (!CanReadFromPath(file))
|
|
||||||
{
|
{
|
||||||
LogErrors<string>($"File access to \"{file}\" is not allowed.");
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var res = _storageService.TryLoadText(file);
|
||||||
|
|
||||||
|
if (res.IsFailed || res is not { Value: { } script})
|
||||||
|
{
|
||||||
|
UnsafeLogErrors($"Failed to load file '{file}'.", res.ToResult());
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_storageService.TryLoadText(file) is not { IsSuccess: true, Value: not null } script)
|
if (script.IsNullOrWhiteSpace())
|
||||||
{
|
{
|
||||||
LogErrors<string>($"Failed to load file \"{file}\".");
|
UnsafeLogErrors($"The file '{file}' is empty. ", res.ToResult());
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (script.Value.IsNullOrWhiteSpace())
|
return script;
|
||||||
{
|
|
||||||
LogErrors<string>($"The file \"{file}\" was empty.");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return script.Value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ClearCaches()
|
public void ClearCaches()
|
||||||
{
|
{
|
||||||
((IService)this).CheckDisposed();
|
IService.CheckDisposed(this);
|
||||||
_storageService?.PurgeCache();
|
_storageService?.PurgeCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Result<ImmutableArray<(ContentPath Path, Result<string>)>>> CacheResourcesAsync(ImmutableArray<ILuaScriptResourceInfo> resourceInfos)
|
public async Task<Result<ImmutableArray<(ContentPath Path, Result<string>)>>> CacheResourcesAsync(ImmutableArray<ILuaScriptResourceInfo> resourceInfos)
|
||||||
{
|
{
|
||||||
// TODO: Needs an async lock?
|
|
||||||
IService.CheckDisposed(this);
|
IService.CheckDisposed(this);
|
||||||
if (!_storageService.UseCaching)
|
if (!_storageService.UseCaching)
|
||||||
|
{
|
||||||
return FluentResults.Result.Fail($"Caching is not enabled.");
|
return FluentResults.Result.Fail($"Caching is not enabled.");
|
||||||
|
}
|
||||||
|
|
||||||
return await this._storageService.LoadPackageTextFilesAsync([..resourceInfos.SelectMany(ri => ri.FilePaths)]);
|
return await this._storageService.LoadPackageTextFilesAsync([..resourceInfos.SelectMany(ri => ri.FilePaths)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool ScriptFileExists(string file)
|
public override bool ScriptFileExists(string file)
|
||||||
{
|
{
|
||||||
((IService)this).CheckDisposed();
|
IService.CheckDisposed(this);
|
||||||
|
|
||||||
if (!CanReadFromPath(file))
|
|
||||||
{
|
|
||||||
LogErrors<string>($"File access to \"{file}\" is not allowed.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var result = _storageService.FileExists(file);
|
var result = _storageService.FileExists(file);
|
||||||
|
|
||||||
if (result is { IsFailed: true })
|
if (result is { IsFailed: true })
|
||||||
{
|
{
|
||||||
LogErrors<string>($"Unable to find and load file \"{file}\".");
|
UnsafeLogErrors($"Unable to find and load file \"{file}\".", result.ToResult());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return result.IsSuccess;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool CanReadFromPath(string file)
|
private void UnsafeLogErrors(string message, FluentResults.Result result = null)
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool CanWriteToPath(string file)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void LogErrors<T>(string message, FluentResults.Result<T> result = null)
|
|
||||||
{
|
{
|
||||||
_loggerService.Value.LogError($"{nameof(LuaScriptLoader)}: {message}");
|
_loggerService.Value.LogError($"{nameof(LuaScriptLoader)}: {message}");
|
||||||
|
if (result is null || result.Errors.Count <= 0)
|
||||||
if (result is null || result.Errors.Count <= 0)
|
{
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var error in result.Errors)
|
foreach (var error in result.Errors)
|
||||||
{
|
{
|
||||||
@@ -117,14 +95,58 @@ namespace Barotrauma.LuaCs.Services.Safe
|
|||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
if (IsDisposed)
|
if (!ModUtils.Threading.CheckIfClearAndSetBool(ref _isDisposed))
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
IsDisposed = true;
|
}
|
||||||
|
|
||||||
_storageService?.Dispose();
|
_storageService?.Dispose();
|
||||||
_loggerService?.Value.Dispose();
|
_loggerService?.Value.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsDisposed { get; private set; }
|
private int _isDisposed = 0;
|
||||||
|
public bool IsDisposed => ModUtils.Threading.GetBool(ref _isDisposed);
|
||||||
|
|
||||||
|
public bool IsFileAccessible(string path, bool readOnly, bool checkWhitelistOnly = true)
|
||||||
|
{
|
||||||
|
IService.CheckDisposed(this);
|
||||||
|
return _storageService.IsFileAccessible(path, readOnly, checkWhitelistOnly);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddFileToWhitelist(string path, bool readOnly = true)
|
||||||
|
{
|
||||||
|
IService.CheckDisposed(this);
|
||||||
|
_storageService.AddFileToWhitelist(path, readOnly);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddFilesToWhitelist(ImmutableArray<string> paths, bool readOnly = true)
|
||||||
|
{
|
||||||
|
IService.CheckDisposed(this);
|
||||||
|
_storageService.AddFilesToWhitelist(paths, readOnly);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveFileFromAllWhitelists(string path)
|
||||||
|
{
|
||||||
|
IService.CheckDisposed(this);
|
||||||
|
_storageService.RemoveFileFromAllWhitelists(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public FluentResults.Result SetReadOnlyWhitelist(ImmutableArray<string> filePaths)
|
||||||
|
{
|
||||||
|
IService.CheckDisposed(this);
|
||||||
|
return _storageService.SetReadOnlyWhitelist(filePaths);
|
||||||
|
}
|
||||||
|
|
||||||
|
public FluentResults.Result SetReadWriteWhitelist(ImmutableArray<string> filePaths)
|
||||||
|
{
|
||||||
|
IService.CheckDisposed(this);
|
||||||
|
return _storageService.SetReadWriteWhitelist(filePaths);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ClearAllWhitelists()
|
||||||
|
{
|
||||||
|
IService.CheckDisposed(this);
|
||||||
|
_storageService.ClearAllWhitelists();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+51
-114
@@ -11,33 +11,47 @@ using Barotrauma.LuaCs.Data;
|
|||||||
using FarseerPhysics.Common;
|
using FarseerPhysics.Common;
|
||||||
using FluentResults;
|
using FluentResults;
|
||||||
using FluentResults.LuaCs;
|
using FluentResults.LuaCs;
|
||||||
|
using Microsoft.Toolkit.Diagnostics;
|
||||||
using Path = System.IO.Path;
|
using Path = System.IO.Path;
|
||||||
|
|
||||||
namespace Barotrauma.LuaCs.Services.Safe;
|
namespace Barotrauma.LuaCs.Services.Safe;
|
||||||
|
|
||||||
public class SafeStorageService : StorageService, ISafeStorageService
|
public class SafeStorageService : StorageService, ISafeStorageService
|
||||||
{
|
{
|
||||||
private ConcurrentDictionary<string, byte> _fileListRead = new (), _fileListWrite = new();
|
private ConcurrentDictionary<string, byte>
|
||||||
|
_fileListRead = new (),
|
||||||
|
_fileListWrite = new();
|
||||||
|
private readonly AsyncReaderWriterLock _higherOperationsLock = new();
|
||||||
|
|
||||||
public SafeStorageService(IStorageServiceConfig configData) : base(configData)
|
public SafeStorageService(IStorageServiceConfig configData) : base(configData)
|
||||||
{
|
{
|
||||||
|
IsReadOperationAllowedEval = async Task<bool> (fp) => IsFileAccessible(fp, true, true);
|
||||||
|
IsWriteOperationAllowedEval = async Task<bool> (fp) => IsFileAccessible(fp, false, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetFullPath(string path) => System.IO.Path.GetFullPath(path).CleanUpPathCrossPlatform();
|
private string GetFullPath(string path) => System.IO.Path.GetFullPath(path).CleanUpPathCrossPlatform();
|
||||||
|
|
||||||
public bool IsFileAccessible(string path, bool readOnly, bool checkWhitelistOnly = true)
|
public bool IsFileAccessible(string path, bool readOnly, bool checkWhitelistOnly = true)
|
||||||
{
|
{
|
||||||
((IService)this).CheckDisposed();
|
Guard.IsNotNullOrWhiteSpace(path, nameof(path));
|
||||||
|
using var lck = _higherOperationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult();
|
||||||
|
IService.CheckDisposed(this);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
path = GetFullPath(path);
|
path = GetFullPath(path);
|
||||||
if (!_fileListRead.ContainsKey(path))
|
if (!_fileListRead.ContainsKey(path))
|
||||||
|
{
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
if (!readOnly && !_fileListWrite.ContainsKey(path))
|
if (!readOnly && !_fileListWrite.ContainsKey(path))
|
||||||
|
{
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
if (checkWhitelistOnly)
|
if (checkWhitelistOnly)
|
||||||
|
{
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
using var fs = System.IO.File.Open(
|
using var fs = System.IO.File.Open(
|
||||||
path, FileMode.Open, readOnly ? FileAccess.Read : FileAccess.ReadWrite, FileShare.ReadWrite);
|
path, FileMode.Open, readOnly ? FileAccess.Read : FileAccess.ReadWrite, FileShare.ReadWrite);
|
||||||
return readOnly ? fs.CanRead : fs.CanWrite;
|
return readOnly ? fs.CanRead : fs.CanWrite;
|
||||||
@@ -50,13 +64,18 @@ public class SafeStorageService : StorageService, ISafeStorageService
|
|||||||
|
|
||||||
public void AddFileToWhitelist(string path, bool readOnly = true)
|
public void AddFileToWhitelist(string path, bool readOnly = true)
|
||||||
{
|
{
|
||||||
((IService)this).CheckDisposed();
|
Guard.IsNotNullOrWhiteSpace(path, nameof(path));
|
||||||
|
using var lck = _higherOperationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult();
|
||||||
|
IService.CheckDisposed(this);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
path = GetFullPath(path);
|
path = GetFullPath(path);
|
||||||
_fileListRead.AddOrUpdate(path, s => 0, (s, b) => 0);
|
_fileListRead.AddOrUpdate(path, s => 0, (s, b) => 0);
|
||||||
if (!readOnly)
|
if (!readOnly)
|
||||||
|
{
|
||||||
_fileListWrite.AddOrUpdate(path, s => 0, (s, b) => 0);
|
_fileListWrite.AddOrUpdate(path, s => 0, (s, b) => 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
@@ -64,9 +83,23 @@ public class SafeStorageService : StorageService, ISafeStorageService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void AddFilesToWhitelist(ImmutableArray<string> paths, bool readOnly = true)
|
||||||
|
{
|
||||||
|
if (paths.IsDefaultOrEmpty)
|
||||||
|
ThrowHelper.ThrowArgumentNullException(nameof(paths));
|
||||||
|
foreach (var path in paths)
|
||||||
|
{
|
||||||
|
AddFileToWhitelist(path, readOnly);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public void RemoveFileFromAllWhitelists(string path)
|
public void RemoveFileFromAllWhitelists(string path)
|
||||||
{
|
{
|
||||||
((IService)this).CheckDisposed();
|
Guard.IsNotNullOrWhiteSpace(path, nameof(path));
|
||||||
|
using var lck = _higherOperationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult();
|
||||||
|
IService.CheckDisposed(this);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
path = GetFullPath(path);
|
path = GetFullPath(path);
|
||||||
@@ -81,13 +114,18 @@ public class SafeStorageService : StorageService, ISafeStorageService
|
|||||||
|
|
||||||
public FluentResults.Result SetReadOnlyWhitelist(ImmutableArray<string> filePaths)
|
public FluentResults.Result SetReadOnlyWhitelist(ImmutableArray<string> filePaths)
|
||||||
{
|
{
|
||||||
((IService)this).CheckDisposed();
|
using var lck = _higherOperationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult();
|
||||||
|
IService.CheckDisposed(this);
|
||||||
if (filePaths.IsDefaultOrEmpty)
|
if (filePaths.IsDefaultOrEmpty)
|
||||||
|
{
|
||||||
return FluentResults.Result.Fail($"{nameof(SetReadOnlyWhitelist)}: FilePaths cannot be empty.");
|
return FluentResults.Result.Fail($"{nameof(SetReadOnlyWhitelist)}: FilePaths cannot be empty.");
|
||||||
|
}
|
||||||
|
|
||||||
_fileListRead.Clear();
|
_fileListRead.Clear();
|
||||||
var res = new FluentResults.Result();
|
var res = new FluentResults.Result();
|
||||||
foreach (var path in filePaths)
|
foreach (var path in filePaths)
|
||||||
{
|
{
|
||||||
|
Guard.IsNotNullOrWhiteSpace(path, nameof(path));
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var p = Path.GetFullPath(path.CleanUpPathCrossPlatform());
|
var p = Path.GetFullPath(path.CleanUpPathCrossPlatform());
|
||||||
@@ -121,14 +159,19 @@ public class SafeStorageService : StorageService, ISafeStorageService
|
|||||||
|
|
||||||
public FluentResults.Result SetReadWriteWhitelist(ImmutableArray<string> filePaths)
|
public FluentResults.Result SetReadWriteWhitelist(ImmutableArray<string> filePaths)
|
||||||
{
|
{
|
||||||
((IService)this).CheckDisposed();
|
|
||||||
if (filePaths.IsDefaultOrEmpty)
|
if (filePaths.IsDefaultOrEmpty)
|
||||||
|
{
|
||||||
return FluentResults.Result.Fail($"{nameof(SetReadOnlyWhitelist)}: FilePaths cannot be empty.");
|
return FluentResults.Result.Fail($"{nameof(SetReadOnlyWhitelist)}: FilePaths cannot be empty.");
|
||||||
|
}
|
||||||
|
using var lck = _higherOperationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult();
|
||||||
|
IService.CheckDisposed(this);
|
||||||
|
|
||||||
_fileListRead.Clear();
|
_fileListRead.Clear();
|
||||||
_fileListWrite.Clear();
|
_fileListWrite.Clear();
|
||||||
var res = new FluentResults.Result();
|
var res = new FluentResults.Result();
|
||||||
foreach (var path in filePaths)
|
foreach (var path in filePaths)
|
||||||
{
|
{
|
||||||
|
Guard.IsNotNullOrWhiteSpace(path, nameof(path));
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var p = Path.GetFullPath(path.CleanUpPathCrossPlatform());
|
var p = Path.GetFullPath(path.CleanUpPathCrossPlatform());
|
||||||
@@ -167,115 +210,9 @@ public class SafeStorageService : StorageService, ISafeStorageService
|
|||||||
|
|
||||||
public void ClearAllWhitelists()
|
public void ClearAllWhitelists()
|
||||||
{
|
{
|
||||||
((IService)this).CheckDisposed();
|
using var lck = _higherOperationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult();
|
||||||
|
IService.CheckDisposed(this);
|
||||||
_fileListRead.Clear();
|
_fileListRead.Clear();
|
||||||
_fileListWrite.Clear();
|
_fileListWrite.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Base_Overrides
|
|
||||||
|
|
||||||
private bool ReadCheck(string path)
|
|
||||||
{
|
|
||||||
return IsFileAccessible(path, true, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool WriteCheck(string path)
|
|
||||||
{
|
|
||||||
return IsFileAccessible(path, false, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override Result<bool> FileExists(string filePath)
|
|
||||||
{
|
|
||||||
if (!ReadCheck(filePath))
|
|
||||||
return FluentResults.Result.Fail("Cannot access file.");
|
|
||||||
return base.FileExists(filePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override Result<byte[]> TryLoadBinary(string filePath)
|
|
||||||
{
|
|
||||||
if (!ReadCheck(filePath))
|
|
||||||
return FluentResults.Result.Fail("Cannot access file.");
|
|
||||||
return base.TryLoadBinary(filePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async Task<Result<byte[]>> TryLoadBinaryAsync(string filePath)
|
|
||||||
{
|
|
||||||
if (!ReadCheck(filePath))
|
|
||||||
return FluentResults.Result.Fail("Cannot access file.");
|
|
||||||
return await base.TryLoadBinaryAsync(filePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override Result<string> TryLoadText(string filePath, Encoding encoding = null)
|
|
||||||
{
|
|
||||||
if (!ReadCheck(filePath))
|
|
||||||
return FluentResults.Result.Fail("Cannot access file.");
|
|
||||||
return base.TryLoadText(filePath, encoding);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async Task<Result<string>> TryLoadTextAsync(string filePath, Encoding encoding = null)
|
|
||||||
{
|
|
||||||
if (!ReadCheck(filePath))
|
|
||||||
return FluentResults.Result.Fail("Cannot access file.");
|
|
||||||
return await base.TryLoadTextAsync(filePath, encoding);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override Result<XDocument> TryLoadXml(string filePath, Encoding encoding = null)
|
|
||||||
{
|
|
||||||
if (!ReadCheck(filePath))
|
|
||||||
return FluentResults.Result.Fail("Cannot access file.");
|
|
||||||
return base.TryLoadXml(filePath, encoding);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async Task<Result<XDocument>> TryLoadXmlAsync(string filePath, Encoding encoding = null)
|
|
||||||
{
|
|
||||||
if (!ReadCheck(filePath))
|
|
||||||
return FluentResults.Result.Fail("Cannot access file.");
|
|
||||||
return await base.TryLoadXmlAsync(filePath, encoding);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override FluentResults.Result TrySaveBinary(string filePath, in byte[] bytes)
|
|
||||||
{
|
|
||||||
if (!WriteCheck(filePath))
|
|
||||||
return FluentResults.Result.Fail("Cannot write to file.");
|
|
||||||
return base.TrySaveBinary(filePath, in bytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async Task<FluentResults.Result> TrySaveBinaryAsync(string filePath, byte[] bytes)
|
|
||||||
{
|
|
||||||
if (!WriteCheck(filePath))
|
|
||||||
return FluentResults.Result.Fail("Cannot write to file.");
|
|
||||||
return await base.TrySaveBinaryAsync(filePath, bytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override FluentResults.Result TrySaveText(string filePath, in string text, Encoding encoding = null)
|
|
||||||
{
|
|
||||||
if (!WriteCheck(filePath))
|
|
||||||
return FluentResults.Result.Fail("Cannot write to file.");
|
|
||||||
return base.TrySaveText(filePath, in text, encoding);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async Task<FluentResults.Result> TrySaveTextAsync(string filePath, string text, Encoding encoding = null)
|
|
||||||
{
|
|
||||||
if (!WriteCheck(filePath))
|
|
||||||
return FluentResults.Result.Fail("Cannot write to file.");
|
|
||||||
return await base.TrySaveTextAsync(filePath, text, encoding);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override FluentResults.Result TrySaveXml(string filePath, in XDocument document, Encoding encoding = null)
|
|
||||||
{
|
|
||||||
if (!WriteCheck(filePath))
|
|
||||||
return FluentResults.Result.Fail("Cannot write to file.");
|
|
||||||
return base.TrySaveXml(filePath, in document, encoding);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async Task<FluentResults.Result> TrySaveXmlAsync(string filePath, XDocument document, Encoding encoding = null)
|
|
||||||
{
|
|
||||||
if (!WriteCheck(filePath))
|
|
||||||
return FluentResults.Result.Fail("Cannot write to file.");
|
|
||||||
return await base.TrySaveXmlAsync(filePath, document, encoding);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,19 +21,54 @@ public class StorageService : IStorageService
|
|||||||
public StorageService(IStorageServiceConfig configData)
|
public StorageService(IStorageServiceConfig configData)
|
||||||
{
|
{
|
||||||
ConfigData = configData;
|
ConfigData = configData;
|
||||||
|
IsReadOperationAllowedEval = async Task<bool> (str) => true;
|
||||||
|
IsWriteOperationAllowedEval = async Task<bool> (str) => true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public StorageService(IStorageServiceConfig configData,
|
||||||
|
Func<string, Task<bool>> isReadOperationAllowedEval,
|
||||||
|
Func<string, Task<bool>> isWriteOperationAllowedEval)
|
||||||
|
{
|
||||||
|
Guard.IsNotNull(isReadOperationAllowedEval, nameof(isReadOperationAllowedEval));
|
||||||
|
Guard.IsNotNull(isWriteOperationAllowedEval, nameof(isWriteOperationAllowedEval));
|
||||||
|
ConfigData = configData;
|
||||||
|
IsReadOperationAllowedEval = isReadOperationAllowedEval;
|
||||||
|
IsWriteOperationAllowedEval = isWriteOperationAllowedEval;
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly ConcurrentDictionary<string, OneOf.OneOf<byte[], string, XDocument>> _fsCache = new();
|
private readonly ConcurrentDictionary<string, OneOf.OneOf<byte[], string, XDocument>> _fsCache = new();
|
||||||
protected readonly IStorageServiceConfig ConfigData;
|
protected readonly IStorageServiceConfig ConfigData;
|
||||||
protected readonly AsyncReaderWriterLock OperationsLock = new();
|
protected readonly AsyncReaderWriterLock OperationsLock = new();
|
||||||
|
|
||||||
|
private Func<string, Task<bool>> _isReadOperationAllowedEval;
|
||||||
|
protected Func<string, Task<bool>> IsReadOperationAllowedEval
|
||||||
|
{
|
||||||
|
get => _isReadOperationAllowedEval;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value is not null)
|
||||||
|
_isReadOperationAllowedEval = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Func<string, Task<bool>> _isWriteOperationAllowedEval;
|
||||||
|
protected Func<string, Task<bool>> IsWriteOperationAllowedEval
|
||||||
|
{
|
||||||
|
get => _isWriteOperationAllowedEval;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value is not null)
|
||||||
|
_isWriteOperationAllowedEval = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public bool IsDisposed => ModUtils.Threading.GetBool(ref _isDisposed);
|
public bool IsDisposed => ModUtils.Threading.GetBool(ref _isDisposed);
|
||||||
private int _isDisposed = 0;
|
private int _isDisposed = 0;
|
||||||
public void Dispose()
|
public virtual void Dispose()
|
||||||
{
|
{
|
||||||
|
using var lck = OperationsLock.AcquireWriterLock().ConfigureAwait(false).GetAwaiter().GetResult();
|
||||||
if (!ModUtils.Threading.CheckIfClearAndSetBool(ref _isDisposed))
|
if (!ModUtils.Threading.CheckIfClearAndSetBool(ref _isDisposed))
|
||||||
return;
|
return;
|
||||||
using var lck = OperationsLock.AcquireWriterLock().ConfigureAwait(false).GetAwaiter().GetResult();
|
|
||||||
_fsCache.Clear();
|
_fsCache.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -254,7 +289,10 @@ public class StorageService : IStorageService
|
|||||||
using var lck = await OperationsLock.AcquireReaderLock();
|
using var lck = await OperationsLock.AcquireReaderLock();
|
||||||
IService.CheckDisposed(this);
|
IService.CheckDisposed(this);
|
||||||
if (!filePath.FullPath.StartsWith(ConfigData.WorkshopModsDirectory) && !filePath.FullPath.StartsWith(ConfigData.LocalModsDirectory))
|
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!");
|
{
|
||||||
|
ThrowHelper.ThrowUnauthorizedAccessException(
|
||||||
|
$"{nameof(LoadPackageData)}: The filepath of `{filePath.FullPath}' is not in a package directory!");
|
||||||
|
}
|
||||||
return await dataLoader(filePath.FullPath);
|
return await dataLoader(filePath.FullPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -269,7 +307,9 @@ public class StorageService : IStorageService
|
|||||||
ImmutableArray<ContentPath> filePaths, Func<string, Task<Result<T>>> dataLoader)
|
ImmutableArray<ContentPath> filePaths, Func<string, Task<Result<T>>> dataLoader)
|
||||||
{
|
{
|
||||||
if (filePaths.IsDefaultOrEmpty)
|
if (filePaths.IsDefaultOrEmpty)
|
||||||
|
{
|
||||||
ThrowHelper.ThrowArgumentNullException($"{nameof(LoadPackageData)}: File paths is empty!");
|
ThrowHelper.ThrowArgumentNullException($"{nameof(LoadPackageData)}: File paths is empty!");
|
||||||
|
}
|
||||||
using var lck = await OperationsLock.AcquireReaderLock();
|
using var lck = await OperationsLock.AcquireReaderLock();
|
||||||
var builder = ImmutableArray.CreateBuilder<(ContentPath, Result<T>)>();
|
var builder = ImmutableArray.CreateBuilder<(ContentPath, Result<T>)>();
|
||||||
foreach (var path in filePaths)
|
foreach (var path in filePaths)
|
||||||
@@ -299,6 +339,7 @@ public class StorageService : IStorageService
|
|||||||
|
|
||||||
public virtual FluentResults.Result<XDocument> TryLoadXml(string filePath, Encoding encoding)
|
public virtual FluentResults.Result<XDocument> TryLoadXml(string filePath, Encoding encoding)
|
||||||
{
|
{
|
||||||
|
Guard.IsNotNullOrWhiteSpace(filePath, nameof(filePath));
|
||||||
using var lck = OperationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult();
|
using var lck = OperationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult();
|
||||||
IService.CheckDisposed(this);
|
IService.CheckDisposed(this);
|
||||||
|
|
||||||
@@ -316,9 +357,15 @@ public class StorageService : IStorageService
|
|||||||
private FluentResults.Result<string> TryLoadText(string filePath) => TryLoadText(filePath, null);
|
private FluentResults.Result<string> TryLoadText(string filePath) => TryLoadText(filePath, null);
|
||||||
public virtual FluentResults.Result<string> TryLoadText(string filePath, Encoding encoding)
|
public virtual FluentResults.Result<string> TryLoadText(string filePath, Encoding encoding)
|
||||||
{
|
{
|
||||||
|
Guard.IsNotNullOrWhiteSpace(filePath, nameof(filePath));
|
||||||
using var lck = OperationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult();
|
using var lck = OperationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult();
|
||||||
IService.CheckDisposed(this);
|
IService.CheckDisposed(this);
|
||||||
|
|
||||||
|
if (IsReadOperationAllowedEval?.Invoke(filePath).ConfigureAwait(false).GetAwaiter().GetResult() is not true)
|
||||||
|
{
|
||||||
|
return FluentResults.Result.Fail($"{nameof(TryLoadText)}: File '{filePath}' is not allowed.");
|
||||||
|
}
|
||||||
|
|
||||||
if (UseCaching && _fsCache.TryGetValue(filePath, out var result)
|
if (UseCaching && _fsCache.TryGetValue(filePath, out var result)
|
||||||
&& result.TryPickT1(out var cachedVal, out _))
|
&& result.TryPickT1(out var cachedVal, out _))
|
||||||
{
|
{
|
||||||
@@ -338,9 +385,15 @@ public class StorageService : IStorageService
|
|||||||
|
|
||||||
public virtual FluentResults.Result<byte[]> TryLoadBinary(string filePath)
|
public virtual FluentResults.Result<byte[]> TryLoadBinary(string filePath)
|
||||||
{
|
{
|
||||||
|
Guard.IsNotNullOrWhiteSpace(filePath, nameof(filePath));
|
||||||
using var lck = OperationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult();
|
using var lck = OperationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult();
|
||||||
IService.CheckDisposed(this);
|
IService.CheckDisposed(this);
|
||||||
|
|
||||||
|
if (IsReadOperationAllowedEval?.Invoke(filePath).ConfigureAwait(false).GetAwaiter().GetResult() is not true)
|
||||||
|
{
|
||||||
|
return FluentResults.Result.Fail($"{nameof(TryLoadBinary)}: File '{filePath}' is not allowed.");
|
||||||
|
}
|
||||||
|
|
||||||
if (UseCaching && _fsCache.TryGetValue(filePath, out var result)
|
if (UseCaching && _fsCache.TryGetValue(filePath, out var result)
|
||||||
&& result.TryPickT0(out var cachedVal, out _))
|
&& result.TryPickT0(out var cachedVal, out _))
|
||||||
{
|
{
|
||||||
@@ -353,7 +406,9 @@ public class StorageService : IStorageService
|
|||||||
fp = System.IO.Path.IsPathRooted(fp) ? fp : System.IO.Path.GetFullPath(fp);
|
fp = System.IO.Path.IsPathRooted(fp) ? fp : System.IO.Path.GetFullPath(fp);
|
||||||
var fileData = System.IO.File.ReadAllBytes(fp);
|
var fileData = System.IO.File.ReadAllBytes(fp);
|
||||||
if (UseCaching)
|
if (UseCaching)
|
||||||
|
{
|
||||||
_fsCache[filePath] = fileData;
|
_fsCache[filePath] = fileData;
|
||||||
|
}
|
||||||
return new FluentResults.Result<byte[]>().WithSuccess($"Loaded file successfully").WithValue(fileData);
|
return new FluentResults.Result<byte[]>().WithSuccess($"Loaded file successfully").WithValue(fileData);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -365,6 +420,11 @@ public class StorageService : IStorageService
|
|||||||
using var lck = OperationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult();
|
using var lck = OperationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult();
|
||||||
IService.CheckDisposed(this);
|
IService.CheckDisposed(this);
|
||||||
|
|
||||||
|
if (IsWriteOperationAllowedEval?.Invoke(filePath).ConfigureAwait(false).GetAwaiter().GetResult() is not true)
|
||||||
|
{
|
||||||
|
return FluentResults.Result.Fail($"{nameof(TrySaveText)}: File '{filePath}' is not allowed.");
|
||||||
|
}
|
||||||
|
|
||||||
string t = text; //copy
|
string t = text; //copy
|
||||||
return IOExceptionsOperationRunner(nameof(TrySaveText), filePath, () =>
|
return IOExceptionsOperationRunner(nameof(TrySaveText), filePath, () =>
|
||||||
{
|
{
|
||||||
@@ -380,11 +440,16 @@ public class StorageService : IStorageService
|
|||||||
|
|
||||||
public virtual FluentResults.Result TrySaveBinary(string filePath, in byte[] bytes)
|
public virtual FluentResults.Result TrySaveBinary(string filePath, in byte[] bytes)
|
||||||
{
|
{
|
||||||
|
Guard.IsNotNullOrWhiteSpace(filePath, nameof(filePath));
|
||||||
Guard.IsNotNull(bytes, nameof(bytes));
|
Guard.IsNotNull(bytes, nameof(bytes));
|
||||||
Guard.HasSizeGreaterThanOrEqualTo(bytes, 1, nameof(bytes));
|
Guard.HasSizeGreaterThanOrEqualTo(bytes, 1, nameof(bytes));
|
||||||
using var lck = OperationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult();
|
using var lck = OperationsLock.AcquireReaderLock().ConfigureAwait(false).GetAwaiter().GetResult();
|
||||||
IService.CheckDisposed(this);
|
IService.CheckDisposed(this);
|
||||||
|
|
||||||
|
if (IsWriteOperationAllowedEval?.Invoke(filePath).ConfigureAwait(false).GetAwaiter().GetResult() is not true)
|
||||||
|
{
|
||||||
|
return FluentResults.Result.Fail($"{nameof(TrySaveBinary)}: File '{filePath}' is not allowed.");
|
||||||
|
}
|
||||||
|
|
||||||
byte[] b = new byte[bytes.Length];
|
byte[] b = new byte[bytes.Length];
|
||||||
System.Buffer.BlockCopy(bytes, 0, b, 0, bytes.Length);
|
System.Buffer.BlockCopy(bytes, 0, b, 0, bytes.Length);
|
||||||
@@ -401,7 +466,14 @@ public class StorageService : IStorageService
|
|||||||
|
|
||||||
public virtual FluentResults.Result<bool> FileExists(string filePath)
|
public virtual FluentResults.Result<bool> FileExists(string filePath)
|
||||||
{
|
{
|
||||||
|
Guard.IsNotNullOrWhiteSpace(filePath, nameof(filePath));
|
||||||
IService.CheckDisposed(this);
|
IService.CheckDisposed(this);
|
||||||
|
// lock not needed
|
||||||
|
if (IsReadOperationAllowedEval?.Invoke(filePath).ConfigureAwait(false).GetAwaiter().GetResult() is not true)
|
||||||
|
{
|
||||||
|
return FluentResults.Result.Fail($"{nameof(FileExists)}: File '{filePath}' is not allowed.");
|
||||||
|
}
|
||||||
|
|
||||||
return IOExceptionsOperationRunner<bool>(nameof(FileExists), filePath, () =>
|
return IOExceptionsOperationRunner<bool>(nameof(FileExists), filePath, () =>
|
||||||
{
|
{
|
||||||
var fp = filePath.CleanUpPath();
|
var fp = filePath.CleanUpPath();
|
||||||
@@ -412,7 +484,14 @@ public class StorageService : IStorageService
|
|||||||
|
|
||||||
public virtual FluentResults.Result<bool> DirectoryExists(string directoryPath)
|
public virtual FluentResults.Result<bool> DirectoryExists(string directoryPath)
|
||||||
{
|
{
|
||||||
|
Guard.IsNotNullOrWhiteSpace(directoryPath, nameof(directoryPath));
|
||||||
IService.CheckDisposed(this);
|
IService.CheckDisposed(this);
|
||||||
|
// lock not needed
|
||||||
|
if (IsReadOperationAllowedEval?.Invoke(directoryPath).ConfigureAwait(false).GetAwaiter().GetResult() is not true)
|
||||||
|
{
|
||||||
|
return FluentResults.Result.Fail($"{nameof(DirectoryExists)}: File '{directoryPath}' is not allowed.");
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var di = new DirectoryInfo(directoryPath);
|
var di = new DirectoryInfo(directoryPath);
|
||||||
@@ -426,10 +505,20 @@ public class StorageService : IStorageService
|
|||||||
|
|
||||||
public virtual async Task<FluentResults.Result<XDocument>> TryLoadXmlAsync(string filePath, Encoding encoding = null)
|
public virtual async Task<FluentResults.Result<XDocument>> TryLoadXmlAsync(string filePath, Encoding encoding = null)
|
||||||
{
|
{
|
||||||
|
Guard.IsNotNullOrWhiteSpace(filePath, nameof(filePath));
|
||||||
|
using var lck = await OperationsLock.AcquireReaderLock();
|
||||||
IService.CheckDisposed(this);
|
IService.CheckDisposed(this);
|
||||||
|
if (await IsReadOperationAllowedEval.Invoke(filePath) is not true)
|
||||||
|
{
|
||||||
|
return FluentResults.Result.Fail($"{nameof(TryLoadXmlAsync)}: File '{filePath}' is not allowed.");
|
||||||
|
}
|
||||||
|
|
||||||
if (UseCaching && _fsCache.TryGetValue(filePath, out var cachedVal)
|
if (UseCaching && _fsCache.TryGetValue(filePath, out var cachedVal)
|
||||||
&& cachedVal.TryPickT2(out var cachedDoc, out _))
|
&& cachedVal.TryPickT2(out var cachedDoc, out _))
|
||||||
|
{
|
||||||
return FluentResults.Result.Ok(cachedDoc);
|
return FluentResults.Result.Ok(cachedDoc);
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await using var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read);
|
await using var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read);
|
||||||
@@ -446,10 +535,19 @@ public class StorageService : IStorageService
|
|||||||
|
|
||||||
public virtual async Task<FluentResults.Result<string>> TryLoadTextAsync(string filePath, Encoding encoding = null)
|
public virtual async Task<FluentResults.Result<string>> TryLoadTextAsync(string filePath, Encoding encoding = null)
|
||||||
{
|
{
|
||||||
|
Guard.IsNotNullOrWhiteSpace(filePath, nameof(filePath));
|
||||||
|
using var lck = await OperationsLock.AcquireReaderLock();
|
||||||
IService.CheckDisposed(this);
|
IService.CheckDisposed(this);
|
||||||
|
if (await IsReadOperationAllowedEval.Invoke(filePath) is not true)
|
||||||
|
{
|
||||||
|
return FluentResults.Result.Fail($"{nameof(TryLoadTextAsync)}: File '{filePath}' is not allowed.");
|
||||||
|
}
|
||||||
|
|
||||||
if (UseCaching && _fsCache.TryGetValue(filePath, out var cachedVal)
|
if (UseCaching && _fsCache.TryGetValue(filePath, out var cachedVal)
|
||||||
&& cachedVal.TryPickT1(out var cachedTxt, out _))
|
&& cachedVal.TryPickT1(out var cachedTxt, out _))
|
||||||
|
{
|
||||||
return FluentResults.Result.Ok(cachedTxt);
|
return FluentResults.Result.Ok(cachedTxt);
|
||||||
|
}
|
||||||
|
|
||||||
return await IOExceptionsOperationRunnerAsync<string>(nameof(TryLoadTextAsync), filePath, async () =>
|
return await IOExceptionsOperationRunnerAsync<string>(nameof(TryLoadTextAsync), filePath, async () =>
|
||||||
{
|
{
|
||||||
@@ -464,7 +562,14 @@ public class StorageService : IStorageService
|
|||||||
|
|
||||||
public virtual async Task<FluentResults.Result<byte[]>> TryLoadBinaryAsync(string filePath)
|
public virtual async Task<FluentResults.Result<byte[]>> TryLoadBinaryAsync(string filePath)
|
||||||
{
|
{
|
||||||
|
Guard.IsNotNullOrWhiteSpace(filePath, nameof(filePath));
|
||||||
|
using var lck = await OperationsLock.AcquireReaderLock();
|
||||||
IService.CheckDisposed(this);
|
IService.CheckDisposed(this);
|
||||||
|
if (await IsReadOperationAllowedEval.Invoke(filePath) is not true)
|
||||||
|
{
|
||||||
|
return FluentResults.Result.Fail($"{nameof(TryLoadBinaryAsync)}: File '{filePath}' is not allowed.");
|
||||||
|
}
|
||||||
|
|
||||||
if (UseCaching && _fsCache.TryGetValue(filePath, out var cachedVal)
|
if (UseCaching && _fsCache.TryGetValue(filePath, out var cachedVal)
|
||||||
&& cachedVal.TryPickT0(out var cachedBin, out _))
|
&& cachedVal.TryPickT0(out var cachedBin, out _))
|
||||||
{
|
{
|
||||||
@@ -479,13 +584,18 @@ public class StorageService : IStorageService
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// method group overload
|
||||||
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> 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)
|
public virtual async Task<FluentResults.Result> TrySaveTextAsync(string filePath, string text, Encoding encoding = null)
|
||||||
{
|
{
|
||||||
Guard.IsNotNullOrWhiteSpace(text, nameof(text));
|
Guard.IsNotNullOrWhiteSpace(text, nameof(text));
|
||||||
using var lck = await OperationsLock.AcquireReaderLock();
|
using var lck = await OperationsLock.AcquireReaderLock();
|
||||||
IService.CheckDisposed(this);
|
IService.CheckDisposed(this);
|
||||||
|
if (await IsWriteOperationAllowedEval.Invoke(filePath) is not true)
|
||||||
|
{
|
||||||
|
return FluentResults.Result.Fail($"{nameof(TrySaveTextAsync)}: File '{filePath}' is not allowed.");
|
||||||
|
}
|
||||||
|
|
||||||
string t = text.ToString(); //copy
|
string t = text.ToString(); //copy
|
||||||
return await IOExceptionsOperationRunnerAsync(nameof(TrySaveText), filePath, async () =>
|
return await IOExceptionsOperationRunnerAsync(nameof(TrySaveText), filePath, async () =>
|
||||||
{
|
{
|
||||||
@@ -500,11 +610,15 @@ public class StorageService : IStorageService
|
|||||||
|
|
||||||
public virtual async Task<FluentResults.Result> TrySaveBinaryAsync(string filePath, byte[] bytes)
|
public virtual async Task<FluentResults.Result> TrySaveBinaryAsync(string filePath, byte[] bytes)
|
||||||
{
|
{
|
||||||
|
Guard.IsNotNullOrWhiteSpace(filePath, nameof(filePath));
|
||||||
Guard.IsNotNull(bytes, nameof(bytes));
|
Guard.IsNotNull(bytes, nameof(bytes));
|
||||||
Guard.HasSizeGreaterThanOrEqualTo(bytes, 1, nameof(bytes));
|
Guard.HasSizeGreaterThanOrEqualTo(bytes, 1, nameof(bytes));
|
||||||
using var lck = await OperationsLock.AcquireReaderLock();
|
using var lck = await OperationsLock.AcquireReaderLock();
|
||||||
IService.CheckDisposed(this);
|
IService.CheckDisposed(this);
|
||||||
|
if (await IsWriteOperationAllowedEval.Invoke(filePath) is not true)
|
||||||
|
{
|
||||||
|
return FluentResults.Result.Fail($"{nameof(TrySaveBinaryAsync)}: File '{filePath}' is not allowed.");
|
||||||
|
}
|
||||||
|
|
||||||
byte[] b = new byte[bytes.Length];
|
byte[] b = new byte[bytes.Length];
|
||||||
System.Buffer.BlockCopy(bytes, 0, b, 0, bytes.Length);
|
System.Buffer.BlockCopy(bytes, 0, b, 0, bytes.Length);
|
||||||
|
|||||||
Reference in New Issue
Block a user