feat: also use reliable storage for plugin configs

This commit is contained in:
goat 2023-09-27 22:33:58 +02:00
parent 125034155b
commit 1d8b579b04
No known key found for this signature in database
GPG key ID: 49E2AA8C6A76498B
5 changed files with 94 additions and 62 deletions

View file

@ -436,6 +436,10 @@ internal sealed class DalamudConfiguration : IServiceType, IDisposable
{ {
deserialized = deserialized =
JsonConvert.DeserializeObject<DalamudConfiguration>(text, SerializerSettings); JsonConvert.DeserializeObject<DalamudConfiguration>(text, SerializerSettings);
// If this reads as null, the file was empty, that's no good
if (deserialized == null)
throw new Exception("Read config was null.");
}); });
} }
catch (FileNotFoundException) catch (FileNotFoundException)

View file

@ -1,6 +1,6 @@
using System.IO; using System.IO;
using Dalamud.Utility; using Dalamud.Storage;
using Newtonsoft.Json; using Newtonsoft.Json;
namespace Dalamud.Configuration; namespace Dalamud.Configuration;
@ -33,22 +33,36 @@ public sealed class PluginConfigurations
/// <param name="pluginName">Plugin name.</param> /// <param name="pluginName">Plugin name.</param>
public void Save(IPluginConfiguration config, string pluginName) public void Save(IPluginConfiguration config, string pluginName)
{ {
Util.WriteAllTextSafe(this.GetConfigFile(pluginName).FullName, SerializeConfig(config)); Service<ReliableFileStorage>.Get()
.WriteAllText(this.GetConfigFile(pluginName).FullName, SerializeConfig(config));
} }
/// <summary> /// <summary>
/// Load plugin configuration. /// Load plugin configuration.
/// </summary> /// </summary>
/// <param name="pluginName">Plugin name.</param> /// <param name="pluginName">Plugin name.</param>
/// <param name="workingPluginId">WorkingPluginID of the plugin.</param>
/// <returns>Plugin configuration.</returns> /// <returns>Plugin configuration.</returns>
public IPluginConfiguration? Load(string pluginName) public IPluginConfiguration? Load(string pluginName, Guid workingPluginId)
{ {
var path = this.GetConfigFile(pluginName); var path = this.GetConfigFile(pluginName);
if (!path.Exists) IPluginConfiguration? config = null;
return null; try
{
Service<ReliableFileStorage>.Get().ReadAllText(path.FullName, text =>
{
config = DeserializeConfig(text);
if (config == null)
throw new Exception("Read config was null.");
}, workingPluginId);
}
catch (FileNotFoundException)
{
// ignored
}
return DeserializeConfig(File.ReadAllText(path.FullName)); return config;
} }
/// <summary> /// <summary>

View file

@ -370,7 +370,7 @@ public sealed class DalamudPluginInterface : IDisposable
} }
// this shouldn't be a thing, I think, but just in case // this shouldn't be a thing, I think, but just in case
return this.configs.Load(this.plugin.InternalName); return this.configs.Load(this.plugin.InternalName, this.plugin.Manifest.WorkingPluginId);
} }
/// <summary> /// <summary>

View file

@ -3,6 +3,7 @@ using System.Runtime.InteropServices;
using System.Text; using System.Text;
using Dalamud.Logging.Internal; using Dalamud.Logging.Internal;
using Dalamud.Utility;
using PInvoke; using PInvoke;
using SQLite; using SQLite;
@ -32,7 +33,6 @@ public class ReliableFileStorage : IServiceType, IDisposable
/// Initializes a new instance of the <see cref="ReliableFileStorage"/> class. /// Initializes a new instance of the <see cref="ReliableFileStorage"/> class.
/// </summary> /// </summary>
/// <param name="vfsDbPath">Path to the VFS.</param> /// <param name="vfsDbPath">Path to the VFS.</param>
[ServiceManager.ServiceConstructor]
public ReliableFileStorage(string vfsDbPath) public ReliableFileStorage(string vfsDbPath)
{ {
var databasePath = Path.Combine(vfsDbPath, "dalamudVfs.db"); var databasePath = Path.Combine(vfsDbPath, "dalamudVfs.db");
@ -113,7 +113,7 @@ public class ReliableFileStorage : IServiceType, IDisposable
this.db.Update(file); this.db.Update(file);
} }
WriteFileReliably(path, bytes); Util.WriteAllBytesSafe(path, bytes);
} }
/// <summary> /// <summary>
@ -252,39 +252,6 @@ public class ReliableFileStorage : IServiceType, IDisposable
{ {
this.db.Dispose(); this.db.Dispose();
} }
private static void WriteFileReliably(string path, byte[] bytes)
{
ArgumentException.ThrowIfNullOrEmpty(path);
// Open the temp file
var tempPath = path + ".tmp";
using var tempFile = Kernel32
.CreateFile(tempPath.AsSpan(),
new Kernel32.ACCESS_MASK(Kernel32.FileAccess.FILE_GENERIC_READ | Kernel32.FileAccess.FILE_GENERIC_WRITE),
Kernel32.FileShare.None,
null,
Kernel32.CreationDisposition.CREATE_ALWAYS,
Kernel32.CreateFileFlags.FILE_ATTRIBUTE_NORMAL,
Kernel32.SafeObjectHandle.Null);
if (tempFile.IsInvalid)
throw new Win32Exception();
// Write the data
var bytesWritten = Kernel32.WriteFile(tempFile, new ArraySegment<byte>(bytes));
if (bytesWritten != bytes.Length)
throw new Exception($"Could not write all bytes to temp file ({bytesWritten} of {bytes.Length})");
if (!Kernel32.FlushFileBuffers(tempFile))
throw new Win32Exception();
tempFile.Close();
if (!MoveFileEx(tempPath, path, MoveFileFlags.MovefileReplaceExisting | MoveFileFlags.MovefileWriteThrough))
throw new Win32Exception();
}
/// <summary> /// <summary>
/// Replace possible non-portable parts of a path with portable versions. /// Replace possible non-portable parts of a path with portable versions.
@ -299,20 +266,6 @@ public class ReliableFileStorage : IServiceType, IDisposable
return path; return path;
} }
[Flags]
#pragma warning disable SA1201
private enum MoveFileFlags
#pragma warning restore SA1201
{
MovefileReplaceExisting = 0x00000001,
MovefileWriteThrough = 0x00000008,
}
[return: MarshalAs(UnmanagedType.Bool)]
[DllImport("kernel32.dll", SetLastError=true, CharSet=CharSet.Unicode)]
private static extern bool MoveFileEx(string lpExistingFileName, string lpNewFileName,
MoveFileFlags dwFlags);
private class DbFile private class DbFile
{ {

View file

@ -20,6 +20,7 @@ using Dalamud.Logging.Internal;
using Dalamud.Memory; using Dalamud.Memory;
using ImGuiNET; using ImGuiNET;
using Lumina.Excel.GeneratedSheets; using Lumina.Excel.GeneratedSheets;
using PInvoke;
using Serilog; using Serilog;
namespace Dalamud.Utility; namespace Dalamud.Utility;
@ -609,7 +610,7 @@ public static class Util
} }
} }
} }
/// <summary> /// <summary>
/// Overwrite text in a file by first writing it to a temporary file, and then /// Overwrite text in a file by first writing it to a temporary file, and then
/// moving that file to the path specified. /// moving that file to the path specified.
@ -618,12 +619,58 @@ public static class Util
/// <param name="text">The text to write.</param> /// <param name="text">The text to write.</param>
public static void WriteAllTextSafe(string path, string text) public static void WriteAllTextSafe(string path, string text)
{ {
var tmpPath = path + ".tmp"; WriteAllTextSafe(path, text, Encoding.UTF8);
if (File.Exists(tmpPath)) }
File.Delete(tmpPath);
/// <summary>
/// Overwrite text in a file by first writing it to a temporary file, and then
/// moving that file to the path specified.
/// </summary>
/// <param name="path">The path of the file to write to.</param>
/// <param name="text">The text to write.</param>
/// <param name="encoding">Encoding to use.</param>
public static void WriteAllTextSafe(string path, string text, Encoding encoding)
{
WriteAllBytesSafe(path, encoding.GetBytes(text));
}
/// <summary>
/// Overwrite data in a file by first writing it to a temporary file, and then
/// moving that file to the path specified.
/// </summary>
/// <param name="path">The path of the file to write to.</param>
/// <param name="bytes">The data to write.</param>
public static void WriteAllBytesSafe(string path, byte[] bytes)
{
ArgumentException.ThrowIfNullOrEmpty(path);
// Open the temp file
var tempPath = path + ".tmp";
File.WriteAllText(tmpPath, text); using var tempFile = Kernel32
File.Move(tmpPath, path, true); .CreateFile(tempPath.AsSpan(),
new Kernel32.ACCESS_MASK(Kernel32.FileAccess.FILE_GENERIC_READ | Kernel32.FileAccess.FILE_GENERIC_WRITE),
Kernel32.FileShare.None,
null,
Kernel32.CreationDisposition.CREATE_ALWAYS,
Kernel32.CreateFileFlags.FILE_ATTRIBUTE_NORMAL,
Kernel32.SafeObjectHandle.Null);
if (tempFile.IsInvalid)
throw new Win32Exception();
// Write the data
var bytesWritten = Kernel32.WriteFile(tempFile, new ArraySegment<byte>(bytes));
if (bytesWritten != bytes.Length)
throw new Exception($"Could not write all bytes to temp file ({bytesWritten} of {bytes.Length})");
if (!Kernel32.FlushFileBuffers(tempFile))
throw new Win32Exception();
tempFile.Close();
if (!MoveFileEx(tempPath, path, MoveFileFlags.MovefileReplaceExisting | MoveFileFlags.MovefileWriteThrough))
throw new Win32Exception();
} }
/// <summary> /// <summary>
@ -762,4 +809,18 @@ public static class Util
} }
} }
} }
[Flags]
#pragma warning disable SA1201
private enum MoveFileFlags
#pragma warning restore SA1201
{
MovefileReplaceExisting = 0x00000001,
MovefileWriteThrough = 0x00000008,
}
[return: MarshalAs(UnmanagedType.Bool)]
[DllImport("kernel32.dll", SetLastError=true, CharSet=CharSet.Unicode)]
private static extern bool MoveFileEx(string lpExistingFileName, string lpNewFileName,
MoveFileFlags dwFlags);
} }