Files
Oleksandr Bezdieniezhnykh 6f23120c49 [AZ-364] [AZ-360] Refactor C11+C08: decompose RouteProcessingService
Extracts RouteRegionMatcher, RouteCsvWriter, RouteSummaryWriter,
RouteImageRenderer, TilesZipBuilder, RegionFileCleaner from the
~750-LOC RouteProcessingService god-class. Moves TileInfo to its
own file as a sealed record. Replaces IServiceProvider scope-
locator with a direct IRegionService injection (folds AZ-360 / C08).
Updates DI registration and tests.

Tests: 133 / 133 unit + 5 / 5 smoke green; integration suite exit 0.
Pixel-equivalent stitched route image and byte-equivalent CSV /
summary / ZIP outputs verified through the smoke run.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-11 03:12:49 +03:00

96 lines
3.6 KiB
C#

using System.IO.Compression;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using SatelliteProvider.Common.Configs;
namespace SatelliteProvider.Services.RouteManagement;
// AZ-364 / C11: extracted from RouteProcessingService.CreateTilesZipAsync.
// Owns the route_<id>_tiles.zip path computation and the entry-name
// resolution (relative to StorageConfig.TilesDirectory when the tile lives
// under that root, or by file name otherwise). Behavior preserved verbatim:
// existing zip is overwritten, missing tiles are logged but not fatal.
public class TilesZipBuilder
{
private readonly StorageConfig _storageConfig;
private readonly ILogger<TilesZipBuilder> _logger;
public TilesZipBuilder(IOptions<StorageConfig> storageConfig, ILogger<TilesZipBuilder> logger)
{
ArgumentNullException.ThrowIfNull(storageConfig);
_storageConfig = storageConfig.Value;
_logger = logger;
}
public Task<string> BuildAsync(
Guid routeId,
IEnumerable<TileInfo> tiles,
CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(tiles);
Directory.CreateDirectory(_storageConfig.ReadyDirectory);
var zipFilePath = Path.Combine(_storageConfig.ReadyDirectory, $"route_{routeId}_tiles.zip");
return Task.Run(() =>
{
if (File.Exists(zipFilePath))
{
File.Delete(zipFilePath);
}
using var zipArchive = ZipFile.Open(zipFilePath, ZipArchiveMode.Create);
int addedFiles = 0;
int missingFiles = 0;
var tilesBasePath = _storageConfig.TilesDirectory;
var normalizedBasePath = Path.GetFullPath(tilesBasePath)
.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
foreach (var tile in tiles)
{
if (cancellationToken.IsCancellationRequested)
break;
if (File.Exists(tile.FilePath))
{
try
{
var fullPath = Path.GetFullPath(tile.FilePath);
string entryName;
if (fullPath.StartsWith(normalizedBasePath, StringComparison.OrdinalIgnoreCase))
{
var relativePath = fullPath.Substring(normalizedBasePath.Length + 1);
relativePath = relativePath.Replace(Path.DirectorySeparatorChar, '/');
entryName = "tiles/" + relativePath;
}
else
{
entryName = "tiles/" + Path.GetFileName(tile.FilePath);
}
zipArchive.CreateEntryFromFile(tile.FilePath, entryName, CompressionLevel.Optimal);
addedFiles++;
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Failed to add tile to zip: {FilePath}", tile.FilePath);
}
}
else
{
_logger.LogWarning("Tile file not found for zip: {FilePath}", tile.FilePath);
missingFiles++;
}
}
_logger.LogInformation(
"Tiles zip created: {ZipPath} with {AddedFiles} tiles ({MissingFiles} missing)",
zipFilePath, addedFiles, missingFiles);
return zipFilePath;
}, cancellationToken);
}
}