Implement non-blocking IO saving for databases

This commit is contained in:
JustArchi
2019-07-25 17:09:20 +02:00
parent 0a63bd04e1
commit 178ca64cfa
11 changed files with 236 additions and 203 deletions

View File

@@ -26,13 +26,14 @@ using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using ArchiSteamFarm.Helpers;
using ArchiSteamFarm.Localization;
using ArchiSteamFarm.SteamKit2;
using JetBrains.Annotations;
using Newtonsoft.Json;
namespace ArchiSteamFarm {
public sealed class GlobalDatabase : IDisposable {
public sealed class GlobalDatabase : SerializableFile {
[JsonProperty(Required = Required.DisallowNull)]
public readonly Guid Guid = Guid.NewGuid();
@@ -42,13 +43,23 @@ namespace ArchiSteamFarm {
[JsonProperty(Required = Required.DisallowNull)]
internal readonly InMemoryServerListProvider ServerListProvider = new InMemoryServerListProvider();
private readonly SemaphoreSlim FileSemaphore = new SemaphoreSlim(1, 1);
private readonly SemaphoreSlim PackagesRefreshSemaphore = new SemaphoreSlim(1, 1);
[JsonProperty(PropertyName = "_CellID", Required = Required.DisallowNull)]
internal uint CellID { get; private set; }
internal uint CellID {
get => BackingCellID;
private string FilePath;
set {
if (BackingCellID == value) {
return;
}
BackingCellID = value;
Utilities.InBackground(Save);
}
}
[JsonProperty(PropertyName = "_CellID", Required = Required.DisallowNull)]
private uint BackingCellID;
private GlobalDatabase([NotNull] string filePath) : this() {
if (string.IsNullOrEmpty(filePath)) {
@@ -61,13 +72,15 @@ namespace ArchiSteamFarm {
[JsonConstructor]
private GlobalDatabase() => ServerListProvider.ServerListUpdated += OnServerListUpdated;
public void Dispose() {
public override void Dispose() {
// Events we registered
ServerListProvider.ServerListUpdated -= OnServerListUpdated;
// Those are objects that are always being created if constructor doesn't throw exception
FileSemaphore.Dispose();
PackagesRefreshSemaphore.Dispose();
// Base dispose
base.Dispose();
}
[ItemCanBeNull]
@@ -159,52 +172,14 @@ namespace ArchiSteamFarm {
PackagesData[packageID] = package;
}
await Save().ConfigureAwait(false);
Utilities.InBackground(Save);
} finally {
PackagesRefreshSemaphore.Release();
}
}
internal async Task SetCellID(uint value = 0) {
if (value == CellID) {
return;
}
CellID = value;
await Save().ConfigureAwait(false);
}
private async void OnServerListUpdated(object sender, EventArgs e) => await Save().ConfigureAwait(false);
private async Task Save() {
string json = JsonConvert.SerializeObject(this);
if (string.IsNullOrEmpty(json)) {
ASF.ArchiLogger.LogNullError(nameof(json));
return;
}
string newFilePath = FilePath + ".new";
await FileSemaphore.WaitAsync().ConfigureAwait(false);
try {
// We always want to write entire content to temporary file first, in order to never load corrupted data, also when target file doesn't exist
await RuntimeCompatibility.File.WriteAllTextAsync(newFilePath, json).ConfigureAwait(false);
if (File.Exists(FilePath)) {
File.Replace(newFilePath, FilePath, null);
} else {
File.Move(newFilePath, FilePath);
}
} catch (Exception e) {
ASF.ArchiLogger.LogGenericException(e);
} finally {
FileSemaphore.Release();
}
}
// ReSharper disable UnusedMember.Global
public bool ShouldSerializeCellID() => CellID != 0;
public bool ShouldSerializePackagesData() => PackagesData.Count > 0;