Commit 92edfcc2 authored by Antoni Zaprzała's avatar Antoni Zaprzała

update

parent 276a4432
...@@ -30,12 +30,12 @@ app.MapGet("/files", (LogFileBrowser browser) => ...@@ -30,12 +30,12 @@ app.MapGet("/files", (LogFileBrowser browser) =>
// downloads to working directory a single log file by name // downloads to working directory a single log file by name
// example: curl -OJ http://127.0.0.1:8082/files/download/lcd-2026-07-01_09-18-33.log // example: curl -OJ http://127.0.0.1:8082/files/download/lcd-2026-07-01_09-18-33.log
app.MapGet("/files/download/{name}", (string name, LogFileBrowser browser) => app.MapGet("/files/download/{name}", async (string name, LogFileBrowser browser, CancellationToken ct) =>
{ {
var path = browser.GetFilePath(name); var bytes = await browser.ReadSnapshotAsync(name, ct);
if (path is null) return Results.NotFound(); if (bytes is null) return Results.NotFound();
return Results.File(path, "application/octet-stream", fileDownloadName: name); return Results.File(bytes, "application/octet-stream", fileDownloadName: name);
}); });
// example: curl -OJ http://127.0.0.1:8082/files/download-all // example: curl -OJ http://127.0.0.1:8082/files/download-all
......
...@@ -37,10 +37,37 @@ namespace LcdLogger.Services ...@@ -37,10 +37,37 @@ namespace LcdLogger.Services
public string? GetFilePath(string name) public string? GetFilePath(string name)
{ {
var fullPath = Path.Combine(_options.Directory, name); var safeName = Path.GetFileName(name);
if (string.IsNullOrEmpty(safeName)) return null;
var fullPath = Path.Combine(_options.Directory, safeName);
return File.Exists(fullPath) ? fullPath : null; return File.Exists(fullPath) ? fullPath : null;
} }
public async Task<byte[]?> ReadSnapshotAsync(string name, CancellationToken cancellationToken = default)
{
var path = GetFilePath(name);
if (path is null) return null;
try
{
using var fileStream = new FileStream(
path,
FileMode.Open,
FileAccess.Read,
FileShare.ReadWrite,
bufferSize: 4096,
useAsync: true);
return await ReadExactSnapshotAsync(fileStream, name, fileStream.Length, cancellationToken);
}
catch (IOException e)
{
_logger.LogError(e, "Failed to read snapshot of {FileName}", name);
return null;
}
}
public async Task<byte[]> BuildZipAsync(CancellationToken cancellationToken = default) public async Task<byte[]> BuildZipAsync(CancellationToken cancellationToken = default)
{ {
var directoryInfo = new DirectoryInfo(_options.Directory); var directoryInfo = new DirectoryInfo(_options.Directory);
...@@ -58,6 +85,7 @@ namespace LcdLogger.Services ...@@ -58,6 +85,7 @@ namespace LcdLogger.Services
{ {
try try
{ {
var snapshotLength = file.Length;
var entry = zip.CreateEntry(file.Name, CompressionLevel.Fastest); var entry = zip.CreateEntry(file.Name, CompressionLevel.Fastest);
using (var entryStream = entry.Open()) using (var entryStream = entry.Open())
...@@ -67,7 +95,7 @@ namespace LcdLogger.Services ...@@ -67,7 +95,7 @@ namespace LcdLogger.Services
FileAccess.Read, FileAccess.Read,
FileShare.ReadWrite)) FileShare.ReadWrite))
{ {
await fileStream.CopyToAsync(entryStream, cancellationToken); await CopyExactAsync(fileStream, entryStream, snapshotLength, cancellationToken);
} }
} }
catch (Exception e) catch (Exception e)
...@@ -76,10 +104,49 @@ namespace LcdLogger.Services ...@@ -76,10 +104,49 @@ namespace LcdLogger.Services
} }
} }
} }
// zip.Dispose() na końcu tego bloku → zapisuje footer do buffer (MemoryStream, sync OK)
return buffer.ToArray(); return buffer.ToArray();
} }
private async Task<byte[]> ReadExactSnapshotAsync(Stream source, string name, long length, CancellationToken cancellationToken)
{
var buffer = new byte[length];
var offset = 0;
while (offset < buffer.Length)
{
var read = await source.ReadAsync(buffer.AsMemory(offset), cancellationToken);
if (read == 0) break;
offset += read;
}
if (offset < buffer.Length)
{
_logger.LogWarning(
"Snapshot {FileName} truncated: expected {Expected} bytes, read {Actual}",
name, buffer.Length, offset);
return buffer[..offset];
}
return buffer;
}
private static async Task CopyExactAsync(Stream source, Stream destination, long count, CancellationToken cancellationToken)
{
var buffer = new byte[81920];
var remaining = count;
while (remaining > 0)
{
var toRead = (int)Math.Min(buffer.Length, remaining);
var read = await source.ReadAsync(buffer.AsMemory(0, toRead), cancellationToken);
if (read == 0) break;
await destination.WriteAsync(buffer.AsMemory(0, read), cancellationToken);
remaining -= read;
}
}
} }
} }
...@@ -138,8 +138,13 @@ namespace LcdLogger.Services ...@@ -138,8 +138,13 @@ namespace LcdLogger.Services
// } // }
//} //}
private bool _disposed;
public async ValueTask DisposeAsync() public async ValueTask DisposeAsync()
{ {
if (_disposed) return;
_disposed = true;
try try
{ {
await _fileStream.FlushAsync(); await _fileStream.FlushAsync();
...@@ -147,7 +152,7 @@ namespace LcdLogger.Services ...@@ -147,7 +152,7 @@ namespace LcdLogger.Services
} }
catch(Exception e) catch(Exception e)
{ {
_logger.LogError(e.Message); _logger.LogError(e, "Failed to flush/close log file on dispose");
} }
} }
} }
......
...@@ -30,6 +30,10 @@ ...@@ -30,6 +30,10 @@
_logger.LogError("Failed to write batch {e}", e); _logger.LogError("Failed to write batch {e}", e);
} }
} }
}
catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested)
{
} }
catch (Exception e) catch (Exception e)
{ {
...@@ -37,7 +41,6 @@ ...@@ -37,7 +41,6 @@
} }
finally finally
{ {
await _logFileManager.DisposeAsync();
_logger.LogInformation("LogWriterWorker stopped"); _logger.LogInformation("LogWriterWorker stopped");
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment