emby-toolbox/EmbyToolbox/Services/SafeFileReplaceService.cs
Emby Toolbox 6264b487fe Initial commit: Emby Toolbox (conversion scroll fix, bulk Del for tracks).
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-12 21:33:47 +05:00

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);
}
}
}
}