From 76fc52e042274b23fe16efd3a86fc2c92028437f Mon Sep 17 00:00:00 2001 From: MapleWheels Date: Thu, 12 Dec 2024 17:07:08 -0500 Subject: [PATCH] - Work on storage service. Pre-squash commit. --- .../SharedSource/LuaCs/ModUtils.cs | 5 + .../LuaCs/Services/PluginManagementService.cs | 43 +- .../LuaCs/Services/StorageService.cs | 407 ++++++++++++++++++ .../_Interfaces/IPluginManagementService.cs | 31 +- .../Services/_Interfaces/IStorageService.cs | 58 ++- 5 files changed, 468 insertions(+), 76 deletions(-) create mode 100644 Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/StorageService.cs diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/ModUtils.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/ModUtils.cs index bc181f36e..e903b0f9a 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/ModUtils.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/ModUtils.cs @@ -23,6 +23,11 @@ namespace Barotrauma.LuaCs public static class ModUtils { + public static class Definitions + { + public const string LuaCsForBarotrauma = nameof(LuaCsForBarotrauma); + } + public static class Environment { internal static void SetCurrentThreadAsMain() => MainThreadId = Thread.CurrentThread.ManagedThreadId; diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs index 64f36d63f..31822e438 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs @@ -5,48 +5,7 @@ using Microsoft.CodeAnalysis; namespace Barotrauma.LuaCs.Services; -public class PluginManagementService : IPluginManagementService +public class PluginManagementService : IPluginManagementService, IAssemblyManagementService { - - public void Dispose() - { - throw new System.NotImplementedException(); - } - - public FluentResults.Result Reset() - { - throw new System.NotImplementedException(); - } - - public bool IsAssemblyLoadedGlobal(string friendlyName) - { - throw new System.NotImplementedException(); - } - - public Result> GetTypes(ContentPackage package = null, string namespacePrefix = null, bool includeInterfaces = false, - bool includeAbstractTypes = false, bool includeDefaultContext = true, bool includeExplicitAssembliesOnly = false) - { - throw new System.NotImplementedException(); - } - - public ImmutableArray GetStandardMetadataReferences() - { - throw new System.NotImplementedException(); - } - - public ImmutableArray GetPluginMetadataReferences() - { - throw new System.NotImplementedException(); - } - - public Result> GetCachedAssembliesForPackage(ContentPackage package) - { - throw new System.NotImplementedException(); - } - - public Result> LoadAssemblyResources(ImmutableArray resource) - { - throw new System.NotImplementedException(); - } } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/StorageService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/StorageService.cs new file mode 100644 index 000000000..facf6b395 --- /dev/null +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/StorageService.cs @@ -0,0 +1,407 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.IO; +using System.Reflection; +using System.Security; +using System.Text; +using System.Threading.Tasks; +using System.Xml.Linq; +using Barotrauma.LuaCs.Configuration; +using Barotrauma.LuaCs.Networking; +using Barotrauma.Steam; +using FluentResults; +using FluentResults.LuaCs; +using OneOf.Types; +using Error = FluentResults.Error; +using File = Barotrauma.IO.File; +using Path = Barotrauma.IO.Path; +using Success = OneOf.Types.Success; + +namespace Barotrauma.LuaCs.Services; + +public class StorageService : IStorageService +{ + + public StorageService(Lazy configService) + { + _configService = configService; + } + private readonly Lazy _configService; + private IConfigEntry _kLocalStoragePath = null; + private IConfigEntry _kLocalFilePathRules = null; + private const string _packagePathKeyword = ""; + private readonly string _runLocation = Path.GetDirectoryName(Assembly.GetEntryAssembly()!.Location.CleanUpPath()); + private IConfigEntry LocalStoragePath => _kLocalStoragePath ??= GetOrCreateConfig(nameof(LocalStoragePath), "/Data/Mods"); + private IConfigEntry LocalFilePathRule => _kLocalFilePathRules ??= GetOrCreateConfig(nameof(LocalFilePathRule), _packagePathKeyword); + private IConfigEntry GetOrCreateConfig(string name, string defaultValue) + { + var c = _configService.Value + .GetConfig>(ModUtils.Definitions.LuaCsForBarotrauma, name); + if (c.IsSuccess) + { + return c.Value; + } + else + { + c = _configService.Value.AddConfigEntry( + ModUtils.Definitions.LuaCsForBarotrauma, + name, defaultValue, NetSync.None, valueChangePredicate: (value) => false); + if (c.IsSuccess) + return c.Value; + else + throw new KeyNotFoundException("Cannot find storage value for key: " + name); + } + } + public bool IsDisposed { get; private set; } + + public void Dispose() + { + if (IsDisposed) + return; + IsDisposed = true; + _kLocalStoragePath = null; + _kLocalFilePathRules = null; + } + + public FluentResults.Result LoadLocalXml(ContentPackage package, string localFilePath) + { + var r = LoadLocalText(package, localFilePath); + if (r is { IsSuccess: true, Value: not null }) + return XDocument.Parse(r.Value); + else + { + return r.ToResult(s => null) + .WithError(GetGeneralError(nameof(LoadLocalXml), localFilePath, package)); + } + } + + + public FluentResults.Result LoadLocalBinary(ContentPackage package, string localFilePath) => TryLoadBinary(GetAbsFromLocal(package, localFilePath)); + public FluentResults.Result LoadLocalText(ContentPackage package, string localFilePath) => TryLoadText(GetAbsFromLocal(package, localFilePath)); + public FluentResults.Result SaveLocalXml(ContentPackage package, string localFilePath, XDocument document) => TrySaveXml(GetAbsFromLocal(package, localFilePath), document); + + public FluentResults.Result SaveLocalBinary(ContentPackage package, string localFilePath, in byte[] bytes) => TrySaveBinary(GetAbsFromLocal(package, localFilePath), bytes); + + public FluentResults.Result SaveLocalText(ContentPackage package, string localFilePath, in string text) => TrySaveText(GetAbsFromLocal(package, localFilePath), text); + + public async Task> LoadLocalXmlAsync(ContentPackage package, string localFilePath) + { + var r = await LoadLocalTextAsync(package, localFilePath); + if (r is { IsSuccess: true, Value: not null }) + return XDocument.Parse(r.Value); + else + { + return r.ToResult(s => null) + .WithError(GetGeneralError(nameof(LoadLocalXml), localFilePath, package)); + } + } + + public Task> LoadLocalBinaryAsync(ContentPackage package, string localFilePath) => + TryLoadBinaryAsync(GetAbsFromLocal(package, localFilePath)); + public Task> LoadLocalTextAsync(ContentPackage package, string localFilePath) => TryLoadTextAsync(GetAbsFromLocal(package, localFilePath)); + public Task SaveLocalXmlAsync(ContentPackage package, string localFilePath, XDocument document) => TrySaveXmlAsync(GetAbsFromLocal(package, localFilePath), document); + public Task SaveLocalBinaryAsync(ContentPackage package, string localFilePath, byte[] bytes) => TrySaveBinaryAsync(GetAbsFromLocal(package, localFilePath), bytes); + public Task SaveLocalTextAsync(ContentPackage package, string localFilePath, string text) => TrySaveTextAsync(GetAbsFromLocal(package, localFilePath), text); + public FluentResults.Result LoadPackageXml(ContentPackage package, string localFilePath) => TryLoadXml(Path.GetFullPath(package.Path.CleanUpPath())); + public FluentResults.Result LoadPackageBinary(ContentPackage package, string localFilePath) => TryLoadBinary(Path.GetFullPath(package.Path.CleanUpPath())); + public FluentResults.Result LoadPackageText(ContentPackage package, string localFilePath) => TryLoadText(Path.GetFullPath(package.Path.CleanUpPath())); + public FluentResults.Result> LoadPackageXmlFiles(ContentPackage package, ImmutableArray localFilePaths) + { + ((IService)this).CheckDisposed(); + if (localFilePaths.IsDefaultOrEmpty) + return new FluentResults.Result>().WithError(new ExceptionalError(new ArgumentNullException(nameof(localFilePaths)))); + var builder = ImmutableArray.CreateBuilder(); + foreach (var path in localFilePaths) + { + if (TryLoadXml(path) is { IsSuccess: true, Value: var document }) + { + + } + } + } + + public FluentResults.Result> LoadPackageBinaryFiles(ContentPackage package, ImmutableArray localFilePaths) + { + ((IService)this).CheckDisposed(); + throw new NotImplementedException(); + } + + public FluentResults.Result> LoadPackageTextFiles(ContentPackage package, ImmutableArray localFilePaths) + { + ((IService)this).CheckDisposed(); + throw new NotImplementedException(); + } + + public FluentResults.Result> FindFilesInPackage(ContentPackage package, string localSubfolder, string regexFilter, bool searchRecursively) + { + ((IService)this).CheckDisposed(); + throw new NotImplementedException(); + } + + public Task> LoadPackageXmlAsync(ContentPackage package, string localFilePath) + { + throw new NotImplementedException(); + } + + public Task> LoadPackageBinaryAsync(ContentPackage package, string localFilePath) + { + throw new NotImplementedException(); + } + + public Task> LoadPackageTextAsync(ContentPackage package, string localFilePath) + { + throw new NotImplementedException(); + } + + public Task>> LoadPackageXmlFilesAsync(ContentPackage package, ImmutableArray localFilePaths) + { + throw new NotImplementedException(); + } + + public Task>> LoadPackageBinaryFilesAsync(ContentPackage package, ImmutableArray localFilePaths) + { + throw new NotImplementedException(); + } + + public Task>> LoadPackageTextFilesAsync(ContentPackage package, ImmutableArray localFilePaths) + { + throw new NotImplementedException(); + } + + public FluentResults.Result 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); + else + { + return r.ToResult(s => null) + .WithError(GetGeneralError(nameof(LoadLocalXml), filePath)); + } + } + + public FluentResults.Result TryLoadText(string filePath, Encoding encoding = null) + { + ((IService)this).CheckDisposed(); + return IOExceptionsOperationRunner(nameof(TryLoadText), filePath, () => + { + var fp = filePath.CleanUpPath(); + fp = System.IO.Path.IsPathRooted(fp) ? fp : System.IO.Path.GetFullPath(fp); + var fileText = encoding is null ? System.IO.File.ReadAllText(fp) : System.IO.File.ReadAllText(fp, encoding); + return new FluentResults.Result().WithSuccess($"Loaded file successfully").WithValue(fileText); + }); + } + + public FluentResults.Result TryLoadBinary(string filePath) + { + ((IService)this).CheckDisposed(); + return IOExceptionsOperationRunner(nameof(TryLoadBinary), filePath, () => + { + var fp = filePath.CleanUpPath(); + fp = System.IO.Path.IsPathRooted(fp) ? fp : System.IO.Path.GetFullPath(fp); + var fileData = System.IO.File.ReadAllBytes(fp); + return new FluentResults.Result().WithSuccess($"Loaded file successfully").WithValue(fileData); + }); + } + + public FluentResults.Result TrySaveXml(string filePath, in XDocument document, Encoding encoding = null) + { + ((IService)this).CheckDisposed(); + return IOExceptionsOperationRunner(nameof(TrySaveXml), filePath, () => + { + + }); + } + + public FluentResults.Result TrySaveText(string filePath, in string text, Encoding encoding = null) + { + ((IService)this).CheckDisposed(); + throw new NotImplementedException(); + } + + public FluentResults.Result TrySaveBinary(string filePath, in byte[] bytes) + { + ((IService)this).CheckDisposed(); + throw new NotImplementedException(); + } + + public FluentResults.Result FileExists(string filePath) + { + ((IService)this).CheckDisposed(); + throw new NotImplementedException(); + } + + public async Task> TryLoadXmlAsync(string filePath, Encoding encoding = null) + { + throw new NotImplementedException(); + } + + public async Task> TryLoadTextAsync(string filePath, Encoding encoding = null) + { + throw new NotImplementedException(); + } + + public async Task> TryLoadBinaryAsync(string filePath) + { + throw new NotImplementedException(); + } + + public async Task TrySaveXmlAsync(string filePath, XDocument document, Encoding encoding = null) + { + throw new NotImplementedException(); + } + + public async Task TrySaveTextAsync(string filePath, string text, Encoding encoding = null) + { + throw new NotImplementedException(); + } + + public async Task TrySaveBinaryAsync(string filePath, byte[] bytes) + { + throw new NotImplementedException(); + } + + private async Task> IOExceptionsOperationRunnerAsync(string funcName, string filepath, Func>> operation) + { + try + { + return await operation?.Invoke()!; + } + catch (ArgumentNullException ane) + { + return ReturnException(ane, filepath).WithError(GetGeneralError(funcName, filepath)); + } + catch (ArgumentException ae) + { + return ReturnException(ae, filepath).WithError(GetGeneralError(funcName, filepath)); + } + catch (PathTooLongException ptle) + { + return ReturnException(ptle, filepath).WithError(GetGeneralError(funcName, filepath)); + } + catch (NotSupportedException nse) + { + return ReturnException(nse, filepath).WithError(GetGeneralError(funcName, filepath)); + } + catch (UnauthorizedAccessException uae) + { + return ReturnException(uae, filepath).WithError(GetGeneralError(funcName, filepath)); + } + catch (DirectoryNotFoundException dnfe) + { + return ReturnException(dnfe, filepath).WithError(GetGeneralError(funcName, filepath)); + } + catch (FileNotFoundException fnfe) + { + return ReturnException(fnfe, filepath).WithError(GetGeneralError(funcName, filepath)); + } + catch (SecurityException se) + { + return ReturnException(se, filepath).WithError(GetGeneralError(funcName, filepath)); + } + catch (IOException ioe) + { + return ReturnException(ioe, filepath).WithError(GetGeneralError(nameof(SaveLocalXml), filepath)); + } + } + + private FluentResults.Result IOExceptionsOperationRunner(string funcName, string filepath, Func> operation) + { + try + { + return operation?.Invoke(); + } + catch (ArgumentNullException ane) + { + return ReturnException(ane, filepath) + .WithError(GetGeneralError(funcName, filepath)); + } + catch (ArgumentException ae) + { + return ReturnException(ae, filepath) + .WithError(GetGeneralError(funcName, filepath)); + } + catch (PathTooLongException ptle) + { + return ReturnException(ptle, filepath) + .WithError(GetGeneralError(funcName, filepath)); + } + catch (NotSupportedException nse) + { + return ReturnException(nse, filepath) + .WithError(GetGeneralError(funcName, filepath)); + } + catch (UnauthorizedAccessException uae) + { + return ReturnException(uae, filepath) + .WithError(GetGeneralError(funcName, filepath)); + } + catch (DirectoryNotFoundException dnfe) + { + return ReturnException(dnfe, filepath) + .WithError(GetGeneralError(funcName, filepath)); + } + catch (FileNotFoundException fnfe) + { + return ReturnException(fnfe, filepath) + .WithError(GetGeneralError(funcName, filepath)); + } + catch (SecurityException se) + { + return ReturnException(se, filepath) + .WithError(GetGeneralError(funcName, filepath)); + } + catch (IOException ioe) + { + return ReturnException(ioe, filepath) + .WithError(GetGeneralError(nameof(SaveLocalXml), filepath)); + } + } + + private Error GetGeneralError(string funcName, string localfp, ContentPackage package) => + new Error($"{funcName}: Failed to load local file.") + .WithMetadata(MetadataType.ExceptionObject, this) + .WithMetadata(MetadataType.Sources, localfp) + .WithMetadata(MetadataType.RootObject, package); + + private Error GetGeneralError(string funcName, string localfp) => + new Error($"{funcName}: Failed to load local file.") + .WithMetadata(MetadataType.ExceptionObject, this) + .WithMetadata(MetadataType.Sources, localfp); + + private string GetAbsFromLocal(ContentPackage package, string localFilePath) + { + return System.IO.Path.GetFullPath(System.IO.Path.Combine( + _runLocation, + LocalStoragePath.Value, + LocalFilePathRule.Value.Replace(_packagePathKeyword, package.Name.IsNullOrWhiteSpace() + ? package.TryExtractSteamWorkshopId(out var id) + ? id.Value.ToString() + : "_fallbackFolder" + : package.Name), + localFilePath)); + } + + private FluentResults.Result ReturnException(TException exception, ContentPackage package) where TException : Exception + { + return new FluentResults.Result().WithError(new ExceptionalError(exception) + .WithMetadata(MetadataType.ExceptionObject, this) + .WithMetadata(MetadataType.RootObject, package)); + } + + private FluentResults.Result ReturnException(TException exception, ContentPackage package) where TException : Exception + { + return new FluentResults.Result().WithError(new ExceptionalError(exception) + .WithMetadata(MetadataType.ExceptionObject, this) + .WithMetadata(MetadataType.RootObject, package)); + } + + private FluentResults.Result ReturnException(TException exception, string filePath) where TException : Exception + { + return new FluentResults.Result().WithError(new ExceptionalError(exception) + .WithMetadata(MetadataType.ExceptionObject, this) + .WithMetadata(MetadataType.RootObject, filePath)); + } +} diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPluginManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPluginManagementService.cs index 7769498e7..0a06c13b9 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPluginManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IPluginManagementService.cs @@ -9,14 +9,23 @@ namespace Barotrauma.LuaCs.Services; public interface IPluginManagementService : IReusableService { /// - /// Checks if an assembly with either the fully-qualified name globally or a 'friendly name' within loaded plugins - /// with the given name is loaded. + /// Checks if the supplied resource is currently loaded. /// - /// + /// The resource to check. /// - bool IsAssemblyLoadedGlobal(string friendlyName); + bool IsResourceLoaded(T resource) where T : IAssemblyResourceInfo; - // TODO: Documentation. + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// FluentResults.Result> GetTypes( ContentPackage package = null, string namespacePrefix = null, @@ -24,18 +33,6 @@ public interface IPluginManagementService : IReusableService bool includeAbstractTypes = false, bool includeDefaultContext = true, bool includeExplicitAssembliesOnly = false); - - /// - /// Gets the assembly MetadataReference collection for the BCL and base game assemblies. - /// - /// - ImmutableArray GetStandardMetadataReferences(); - - /// - /// - /// - /// - ImmutableArray GetPluginMetadataReferences(); /// /// diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IStorageService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IStorageService.cs index b43a595ba..1c830ab78 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IStorageService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/_Interfaces/IStorageService.cs @@ -1,42 +1,66 @@ using System.Collections.Immutable; +using System.Text; +using System.Threading.Tasks; using System.Xml.Linq; namespace Barotrauma.LuaCs.Services; -public interface IStorageService : IReusableService +public interface IStorageService : IService { #region LocalGameData FluentResults.Result LoadLocalXml(ContentPackage package, string localFilePath); FluentResults.Result LoadLocalBinary(ContentPackage package, string localFilePath); FluentResults.Result LoadLocalText(ContentPackage package, string localFilePath); - FluentResults.Result FileExistsInLocalData(ContentPackage package, string localFilePath); + FluentResults.Result SaveLocalXml(ContentPackage package, string localFilePath, XDocument document); + FluentResults.Result SaveLocalBinary(ContentPackage package, string localFilePath, in byte[] bytes); + FluentResults.Result SaveLocalText(ContentPackage package, string localFilePath, in string text); + // async + Task> LoadLocalXmlAsync(ContentPackage package, string localFilePath); + Task> LoadLocalBinaryAsync(ContentPackage package, string localFilePath); + Task> LoadLocalTextAsync(ContentPackage package, string localFilePath); + Task SaveLocalXmlAsync(ContentPackage package, string localFilePath, XDocument document); + Task SaveLocalBinaryAsync(ContentPackage package, string localFilePath, byte[] bytes); + Task SaveLocalTextAsync(ContentPackage package, string localFilePath, string text); #endregion #region ContentPackageData - FluentResults.Result LoadPackageXml(ContentPackage package, string localFilePath, out XDocument document); - FluentResults.Result LoadPackageBinary(ContentPackage package, string localFilePath, out byte[] bytes); - FluentResults.Result LoadPackageText(ContentPackage package, string localFilePath, out string text); - - FluentResults.Result> LoadPackageXmlFiles(ContentPackage package, ImmutableArray localFilePath); - FluentResults.Result> TryLoadPackageBinaryFiles(ContentPackage package, ImmutableArray localFilePath); - FluentResults.Result> TryLoadPackageTextFiles(ContentPackage package, ImmutableArray localFilePath); - + // singles + FluentResults.Result LoadPackageXml(ContentPackage package, string localFilePath); + FluentResults.Result LoadPackageBinary(ContentPackage package, string localFilePath); + FluentResults.Result LoadPackageText(ContentPackage package, string localFilePath); + // collections + FluentResults.Result> LoadPackageXmlFiles(ContentPackage package, ImmutableArray localFilePaths); + FluentResults.Result> LoadPackageBinaryFiles(ContentPackage package, ImmutableArray localFilePaths); + FluentResults.Result> LoadPackageTextFiles(ContentPackage package, ImmutableArray localFilePaths); FluentResults.Result> FindFilesInPackage(ContentPackage package, string localSubfolder, string regexFilter, bool searchRecursively); - FluentResults.Result FileExistsInPackage(ContentPackage package, string localFilePath); + // async + // singles + Task> LoadPackageXmlAsync(ContentPackage package, string localFilePath); + Task> LoadPackageBinaryAsync(ContentPackage package, string localFilePath); + Task> LoadPackageTextAsync(ContentPackage package, string localFilePath); + // collections + Task>> LoadPackageXmlFilesAsync(ContentPackage package, ImmutableArray localFilePaths); + Task>> LoadPackageBinaryFilesAsync(ContentPackage package, ImmutableArray localFilePaths); + Task>> LoadPackageTextFilesAsync(ContentPackage package, ImmutableArray localFilePaths); #endregion #region AbsolutePaths - - FluentResults.Result TryLoadXml(string filePatht); - FluentResults.Result TrySaveXml(string filePath, in XDocument document); + FluentResults.Result TryLoadXml(string filePath, Encoding encoding = null); + FluentResults.Result TryLoadText(string filePath, Encoding encoding = null); FluentResults.Result TryLoadBinary(string filePath); + FluentResults.Result TrySaveXml(string filePath, in XDocument document, Encoding encoding = null); + FluentResults.Result TrySaveText(string filePath, in string text, Encoding encoding = null); FluentResults.Result TrySaveBinary(string filePath, in byte[] bytes); - FluentResults.Result TryLoadText(string filePath); - FluentResults.Result TrySaveText(string filePath, string text); FluentResults.Result FileExists(string filePath); - + //async + Task> TryLoadXmlAsync(string filePath, Encoding encoding = null); + Task> TryLoadTextAsync(string filePath, Encoding encoding = null); + Task> TryLoadBinaryAsync(string filePath); + Task TrySaveXmlAsync(string filePath, XDocument document, Encoding encoding = null); + Task TrySaveTextAsync(string filePath, string text, Encoding encoding = null); + Task TrySaveBinaryAsync(string filePath, byte[] bytes); #endregion }