feat: update Godot integration and coordinated package dependencies
This commit is contained in:
11 files changed
+525
-49
No files matched your search
+159
-40
@@ -8,6 +8,12 @@ using Cysharp.Threading.Tasks;
|
||||
|
||||
namespace ShrinkDataSaver
|
||||
{
|
||||
public enum LocalStorageCommitStrategy
|
||||
{
|
||||
FileReplace = 0,
|
||||
RenameOnly = 1
|
||||
}
|
||||
|
||||
public class LocalStorageProvider : IStorageProvider
|
||||
{
|
||||
private const string BackupPrimarySuffix = ".bak1";
|
||||
@@ -18,16 +24,37 @@ namespace ShrinkDataSaver
|
||||
new(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
private readonly string _rootPath;
|
||||
private readonly LocalStorageCommitStrategy _commitStrategy;
|
||||
private readonly Action<string, string> _overwriteMove;
|
||||
|
||||
public LocalStorageProvider(string rootPath = null)
|
||||
: this(rootPath, LocalStorageCommitStrategy.FileReplace)
|
||||
{
|
||||
}
|
||||
|
||||
public LocalStorageProvider(string rootPath, LocalStorageCommitStrategy commitStrategy)
|
||||
: this(rootPath, commitStrategy, null)
|
||||
{
|
||||
}
|
||||
|
||||
public LocalStorageProvider(string rootPath, LocalStorageCommitStrategy commitStrategy,
|
||||
Action<string, string> overwriteMove)
|
||||
{
|
||||
if (!Enum.IsDefined(typeof(LocalStorageCommitStrategy), commitStrategy))
|
||||
throw new ArgumentOutOfRangeException(nameof(commitStrategy));
|
||||
if (commitStrategy == LocalStorageCommitStrategy.RenameOnly && overwriteMove == null)
|
||||
throw new ArgumentException("RenameOnly requires an overwrite-rename implementation.", nameof(overwriteMove));
|
||||
_overwriteMove = overwriteMove;
|
||||
_rootPath = rootPath ?? ShrinkDataSaverPlatform.Paths?.PersistentDataPath
|
||||
#if UNITY_5_3_OR_NEWER
|
||||
?? UnityEngine.Application.persistentDataPath
|
||||
#endif
|
||||
?? throw new InvalidOperationException("ShrinkDataSaver persistent path is not configured.");
|
||||
_commitStrategy = commitStrategy;
|
||||
}
|
||||
|
||||
public LocalStorageCommitStrategy CommitStrategy => _commitStrategy;
|
||||
|
||||
private string Resolve(string path) =>
|
||||
Path.IsPathRooted(path) ? path : Path.Combine(_rootPath, path);
|
||||
|
||||
@@ -40,47 +67,25 @@ namespace ShrinkDataSaver
|
||||
await Task.Run(() => Directory.CreateDirectory(dir), ct);
|
||||
}
|
||||
|
||||
var tempPath = fullPath + TempWriteSuffix;
|
||||
var backupPath = fullPath + BackupPrimarySuffix;
|
||||
var secondaryBackupPath = fullPath + BackupSecondarySuffix;
|
||||
var pathLock = GetPathLock(fullPath);
|
||||
|
||||
await pathLock.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
await WriteFileBytesAsync(tempPath, data, ct);
|
||||
await Task.Run(() =>
|
||||
{
|
||||
if (File.Exists(fullPath))
|
||||
{
|
||||
if (File.Exists(secondaryBackupPath))
|
||||
{
|
||||
File.Delete(secondaryBackupPath);
|
||||
}
|
||||
await Task.Run(() => RecoverInterruptedWrite(fullPath), CancellationToken.None);
|
||||
ct.ThrowIfCancellationRequested();
|
||||
|
||||
if (File.Exists(backupPath))
|
||||
{
|
||||
File.Move(backupPath, secondaryBackupPath);
|
||||
}
|
||||
|
||||
File.Replace(tempPath, fullPath, backupPath, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
File.Move(tempPath, fullPath);
|
||||
}
|
||||
}, ct);
|
||||
}
|
||||
catch
|
||||
{
|
||||
await Task.Run(() =>
|
||||
var tempPath = fullPath + TempWriteSuffix;
|
||||
try
|
||||
{
|
||||
if (File.Exists(tempPath))
|
||||
{
|
||||
File.Delete(tempPath);
|
||||
}
|
||||
}, CancellationToken.None);
|
||||
throw;
|
||||
await WriteFileBytesAsync(tempPath, data, ct);
|
||||
ct.ThrowIfCancellationRequested();
|
||||
await Task.Run(() => Commit(tempPath, fullPath), CancellationToken.None);
|
||||
}
|
||||
catch
|
||||
{
|
||||
await Task.Run(() => TryDelete(tempPath), CancellationToken.None);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -95,6 +100,7 @@ namespace ShrinkDataSaver
|
||||
await pathLock.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
await Task.Run(() => RecoverInterruptedWrite(fullPath), CancellationToken.None);
|
||||
if (!File.Exists(fullPath))
|
||||
{
|
||||
throw new FileNotFoundException($"ShrinkDataSaver: file not found: {fullPath}");
|
||||
@@ -109,7 +115,22 @@ namespace ShrinkDataSaver
|
||||
}
|
||||
|
||||
public async UniTask<bool> ExistsAsync(string path, CancellationToken ct = default) =>
|
||||
await Task.Run(() => File.Exists(Resolve(path)), ct);
|
||||
await ExistsCoreAsync(Resolve(path), ct);
|
||||
|
||||
private async UniTask<bool> ExistsCoreAsync(string fullPath, CancellationToken ct)
|
||||
{
|
||||
var pathLock = GetPathLock(fullPath);
|
||||
await pathLock.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
await Task.Run(() => RecoverInterruptedWrite(fullPath), CancellationToken.None);
|
||||
return File.Exists(fullPath);
|
||||
}
|
||||
finally
|
||||
{
|
||||
pathLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public async UniTask DeleteAsync(string path, CancellationToken ct = default)
|
||||
{
|
||||
@@ -120,10 +141,10 @@ namespace ShrinkDataSaver
|
||||
{
|
||||
await Task.Run(() =>
|
||||
{
|
||||
if (File.Exists(fullPath))
|
||||
{
|
||||
File.Delete(fullPath);
|
||||
}
|
||||
File.Delete(fullPath + TempWriteSuffix);
|
||||
File.Delete(fullPath + BackupPrimarySuffix);
|
||||
File.Delete(fullPath + BackupSecondarySuffix);
|
||||
File.Delete(fullPath);
|
||||
}, ct);
|
||||
}
|
||||
finally
|
||||
@@ -148,6 +169,104 @@ namespace ShrinkDataSaver
|
||||
}, ct);
|
||||
}
|
||||
|
||||
private void Commit(string tempPath, string fullPath)
|
||||
{
|
||||
if (_commitStrategy == LocalStorageCommitStrategy.RenameOnly)
|
||||
CommitRenameOnly(tempPath, fullPath);
|
||||
else
|
||||
CommitFileReplace(tempPath, fullPath);
|
||||
}
|
||||
|
||||
private static void CommitFileReplace(string tempPath, string fullPath)
|
||||
{
|
||||
var backupPath = fullPath + BackupPrimarySuffix;
|
||||
var secondaryBackupPath = fullPath + BackupSecondarySuffix;
|
||||
if (!File.Exists(fullPath))
|
||||
{
|
||||
File.Move(tempPath, fullPath);
|
||||
return;
|
||||
}
|
||||
|
||||
if (File.Exists(secondaryBackupPath))
|
||||
{
|
||||
File.Delete(secondaryBackupPath);
|
||||
}
|
||||
|
||||
if (File.Exists(backupPath))
|
||||
{
|
||||
File.Move(backupPath, secondaryBackupPath);
|
||||
}
|
||||
|
||||
File.Replace(tempPath, fullPath, backupPath, true);
|
||||
}
|
||||
|
||||
private void CommitRenameOnly(string tempPath, string fullPath)
|
||||
{
|
||||
var backupPath = fullPath + BackupPrimarySuffix;
|
||||
var secondaryBackupPath = fullPath + BackupSecondarySuffix;
|
||||
if (File.Exists(fullPath))
|
||||
{
|
||||
if (File.Exists(backupPath))
|
||||
{
|
||||
if (File.Exists(secondaryBackupPath))
|
||||
{
|
||||
File.Delete(secondaryBackupPath);
|
||||
}
|
||||
|
||||
MoveFileReplacing(backupPath, secondaryBackupPath);
|
||||
}
|
||||
|
||||
MoveFileReplacing(fullPath, backupPath);
|
||||
}
|
||||
|
||||
MoveFileReplacing(tempPath, fullPath);
|
||||
}
|
||||
|
||||
private void RecoverInterruptedWrite(string fullPath)
|
||||
{
|
||||
if (_commitStrategy != LocalStorageCommitStrategy.RenameOnly)
|
||||
return;
|
||||
|
||||
var tempPath = fullPath + TempWriteSuffix;
|
||||
var primaryBackupPath = fullPath + BackupPrimarySuffix;
|
||||
var secondaryBackupPath = fullPath + BackupSecondarySuffix;
|
||||
if (!File.Exists(fullPath))
|
||||
{
|
||||
if (File.Exists(primaryBackupPath))
|
||||
{
|
||||
MoveFileReplacing(primaryBackupPath, fullPath);
|
||||
}
|
||||
else if (File.Exists(secondaryBackupPath))
|
||||
{
|
||||
MoveFileReplacing(secondaryBackupPath, fullPath);
|
||||
}
|
||||
}
|
||||
|
||||
if (File.Exists(tempPath))
|
||||
{
|
||||
TryDelete(tempPath);
|
||||
}
|
||||
}
|
||||
|
||||
private void MoveFileReplacing(string source, string destination) => _overwriteMove(source, destination);
|
||||
|
||||
private static void TryDelete(string path)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (File.Exists(path))
|
||||
{
|
||||
File.Delete(path);
|
||||
}
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private static SemaphoreSlim GetPathLock(string fullPath)
|
||||
{
|
||||
return PathLocks.GetOrAdd(fullPath, _ => new SemaphoreSlim(1, 1));
|
||||
|
||||
Reference in new issue
Block a user