130 lines
4.0 KiB
C#
130 lines
4.0 KiB
C#
using System.IO;
|
|
|
|
namespace EmbyToolbox.Services;
|
|
|
|
public sealed class SafeFileReplaceService
|
|
{
|
|
public async Task ReplaceAsync(
|
|
string originalPath,
|
|
string finalPath,
|
|
string tempOutputPath,
|
|
IProgress<int>? progress,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
if (!File.Exists(tempOutputPath))
|
|
{
|
|
throw new IOException("Временный файл результата не найден.");
|
|
}
|
|
|
|
var tempInfo = new FileInfo(tempOutputPath);
|
|
if (tempInfo.Length <= 0)
|
|
{
|
|
throw new IOException("Временный файл результата пуст.");
|
|
}
|
|
|
|
var backupPath = originalPath + ".bak_replace_tmp";
|
|
var finalBackupPath = finalPath + ".bak_existing_tmp";
|
|
if (File.Exists(backupPath))
|
|
{
|
|
File.Delete(backupPath);
|
|
}
|
|
|
|
if (File.Exists(finalBackupPath))
|
|
{
|
|
File.Delete(finalBackupPath);
|
|
}
|
|
|
|
File.Move(originalPath, backupPath, overwrite: false);
|
|
try
|
|
{
|
|
if (!string.Equals(originalPath, finalPath, StringComparison.OrdinalIgnoreCase) && File.Exists(finalPath))
|
|
{
|
|
File.Move(finalPath, finalBackupPath, overwrite: false);
|
|
}
|
|
|
|
var sameRoot = string.Equals(Path.GetPathRoot(finalPath), Path.GetPathRoot(tempOutputPath), StringComparison.OrdinalIgnoreCase);
|
|
if (sameRoot)
|
|
{
|
|
File.Move(tempOutputPath, finalPath, overwrite: true);
|
|
progress?.Report(99);
|
|
}
|
|
else
|
|
{
|
|
await CopyWithProgressAsync(tempOutputPath, finalPath, progress, cancellationToken).ConfigureAwait(false);
|
|
File.Delete(tempOutputPath);
|
|
}
|
|
|
|
var outInfo = new FileInfo(finalPath);
|
|
if (!outInfo.Exists || outInfo.Length <= 0)
|
|
{
|
|
throw new IOException("Новый файл после замены невалиден.");
|
|
}
|
|
|
|
File.Delete(backupPath);
|
|
if (File.Exists(finalBackupPath))
|
|
{
|
|
File.Delete(finalBackupPath);
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
try
|
|
{
|
|
if (File.Exists(finalPath))
|
|
{
|
|
var i = new FileInfo(finalPath);
|
|
if (i.Length == 0)
|
|
{
|
|
File.Delete(finalPath);
|
|
}
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// ignore
|
|
}
|
|
|
|
if (File.Exists(backupPath))
|
|
{
|
|
File.Move(backupPath, originalPath, overwrite: true);
|
|
}
|
|
|
|
if (File.Exists(finalBackupPath))
|
|
{
|
|
File.Move(finalBackupPath, finalPath, overwrite: true);
|
|
}
|
|
|
|
throw;
|
|
}
|
|
}
|
|
|
|
private static async Task CopyWithProgressAsync(string src, string dst, IProgress<int>? progress, CancellationToken token)
|
|
{
|
|
const int bufferSize = 1024 * 1024;
|
|
await using var input = new FileStream(src, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize, useAsync: true);
|
|
await using var output = new FileStream(dst, FileMode.Create, FileAccess.Write, FileShare.None, bufferSize, useAsync: true);
|
|
var total = input.Length;
|
|
var copied = 0L;
|
|
var buffer = new byte[bufferSize];
|
|
while (true)
|
|
{
|
|
var read = await input.ReadAsync(buffer, token).ConfigureAwait(false);
|
|
if (read <= 0)
|
|
{
|
|
break;
|
|
}
|
|
|
|
await output.WriteAsync(buffer.AsMemory(0, read), token).ConfigureAwait(false);
|
|
copied += read;
|
|
if (total > 0)
|
|
{
|
|
var frac = copied / (double)total;
|
|
var extra = frac >= 1.0 ? 9 : (int)Math.Floor(frac * 10.0);
|
|
extra = Math.Clamp(extra, 0, 9);
|
|
progress?.Report(90 + extra);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|