Detach EquipmentBox event before programmatically setting equipment to prevent async UpdateDrawingDropdownAsync from clearing the drawing selection and duplicating entries. Also update ExportRecord.PdfContentHash in StorePdfAsync so the web frontend can serve PDF downloads. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
160 lines
6.2 KiB
C#
160 lines
6.2 KiB
C#
using FabWorks.Api.Configuration;
|
|
using FabWorks.Core.Data;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.Extensions.Options;
|
|
|
|
namespace FabWorks.Api.Services
|
|
{
|
|
public class FileUploadResult
|
|
{
|
|
public string ContentHash { get; set; }
|
|
public string FileName { get; set; }
|
|
public bool WasUnchanged { get; set; }
|
|
public bool IsNewFile { get; set; }
|
|
}
|
|
|
|
public interface IFileStorageService
|
|
{
|
|
string OutputFolder { get; }
|
|
Task<FileUploadResult> StoreDxfAsync(Stream stream, string equipment, string drawingNo, string itemNo, string contentHash);
|
|
Task<FileUploadResult> StorePdfAsync(Stream stream, string equipment, string drawingNo, string contentHash, int? exportRecordId = null);
|
|
Stream OpenBlob(string contentHash, string extension);
|
|
bool BlobExists(string contentHash, string extension);
|
|
}
|
|
|
|
public class FileStorageService : IFileStorageService
|
|
{
|
|
private readonly FileStorageOptions _options;
|
|
private readonly FabWorksDbContext _db;
|
|
|
|
public string OutputFolder => _options.OutputFolder;
|
|
|
|
public FileStorageService(IOptions<FileStorageOptions> options, FabWorksDbContext db)
|
|
{
|
|
_options = options.Value;
|
|
_db = db;
|
|
|
|
var blobRoot = Path.Combine(_options.OutputFolder, "blobs");
|
|
if (!Directory.Exists(blobRoot))
|
|
Directory.CreateDirectory(blobRoot);
|
|
}
|
|
|
|
public async Task<FileUploadResult> StoreDxfAsync(Stream stream, string equipment, string drawingNo, string itemNo, string contentHash)
|
|
{
|
|
var fileName = BuildDxfFileName(drawingNo, equipment, itemNo);
|
|
|
|
// Look up previous hash by drawing number + item number
|
|
var drawingNumber = BuildDrawingNumber(equipment, drawingNo);
|
|
var previousHash = await _db.CutTemplates
|
|
.Where(c => c.BomItem.ExportRecord.DrawingNumber == drawingNumber
|
|
&& c.BomItem.ItemNo == itemNo
|
|
&& c.ContentHash != null)
|
|
.OrderByDescending(c => c.Id)
|
|
.Select(c => c.ContentHash)
|
|
.FirstOrDefaultAsync();
|
|
|
|
var wasUnchanged = previousHash != null && previousHash == contentHash;
|
|
var isNewFile = await StoreBlobAsync(stream, contentHash, "dxf");
|
|
|
|
return new FileUploadResult
|
|
{
|
|
ContentHash = contentHash,
|
|
FileName = fileName,
|
|
WasUnchanged = wasUnchanged,
|
|
IsNewFile = isNewFile
|
|
};
|
|
}
|
|
|
|
public async Task<FileUploadResult> StorePdfAsync(Stream stream, string equipment, string drawingNo, string contentHash, int? exportRecordId = null)
|
|
{
|
|
var drawingNumber = BuildDrawingNumber(equipment, drawingNo);
|
|
var fileName = $"{drawingNumber}.pdf";
|
|
|
|
// Look up previous PDF hash
|
|
var previousHash = await _db.ExportRecords
|
|
.Where(r => r.DrawingNumber == drawingNumber
|
|
&& r.PdfContentHash != null
|
|
&& (exportRecordId == null || r.Id != exportRecordId))
|
|
.OrderByDescending(r => r.Id)
|
|
.Select(r => r.PdfContentHash)
|
|
.FirstOrDefaultAsync();
|
|
|
|
var wasUnchanged = previousHash != null && previousHash == contentHash;
|
|
var isNewFile = await StoreBlobAsync(stream, contentHash, "pdf");
|
|
|
|
// Update the export record with the PDF content hash
|
|
if (exportRecordId.HasValue)
|
|
{
|
|
var record = await _db.ExportRecords.FindAsync(exportRecordId.Value);
|
|
if (record != null)
|
|
{
|
|
record.PdfContentHash = contentHash;
|
|
await _db.SaveChangesAsync();
|
|
}
|
|
}
|
|
|
|
return new FileUploadResult
|
|
{
|
|
ContentHash = contentHash,
|
|
FileName = fileName,
|
|
WasUnchanged = wasUnchanged,
|
|
IsNewFile = isNewFile
|
|
};
|
|
}
|
|
|
|
public Stream OpenBlob(string contentHash, string extension)
|
|
{
|
|
var path = GetBlobPath(contentHash, extension);
|
|
if (!File.Exists(path))
|
|
return null;
|
|
return new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
|
|
}
|
|
|
|
public bool BlobExists(string contentHash, string extension)
|
|
{
|
|
return File.Exists(GetBlobPath(contentHash, extension));
|
|
}
|
|
|
|
private async Task<bool> StoreBlobAsync(Stream stream, string contentHash, string extension)
|
|
{
|
|
var blobPath = GetBlobPath(contentHash, extension);
|
|
|
|
if (File.Exists(blobPath))
|
|
return false; // blob already exists (dedup)
|
|
|
|
var dir = Path.GetDirectoryName(blobPath);
|
|
if (!Directory.Exists(dir))
|
|
Directory.CreateDirectory(dir);
|
|
|
|
using var fileStream = new FileStream(blobPath, FileMode.Create, FileAccess.Write);
|
|
await stream.CopyToAsync(fileStream);
|
|
return true; // new blob written
|
|
}
|
|
|
|
private string GetBlobPath(string contentHash, string extension)
|
|
{
|
|
var prefix1 = contentHash[..2];
|
|
var prefix2 = contentHash[2..4];
|
|
return Path.Combine(_options.OutputFolder, "blobs", prefix1, prefix2, $"{contentHash}.{extension}");
|
|
}
|
|
|
|
private static string BuildDrawingNumber(string equipment, string drawingNo)
|
|
{
|
|
if (!string.IsNullOrEmpty(equipment) && !string.IsNullOrEmpty(drawingNo))
|
|
return $"{equipment} {drawingNo}";
|
|
if (!string.IsNullOrEmpty(equipment))
|
|
return equipment;
|
|
return drawingNo ?? "";
|
|
}
|
|
|
|
private static string BuildDxfFileName(string drawingNo, string equipment, string itemNo)
|
|
{
|
|
var drawingNumber = BuildDrawingNumber(equipment, drawingNo);
|
|
var paddedItem = (itemNo ?? "").PadLeft(2, '0');
|
|
if (!string.IsNullOrEmpty(drawingNumber) && !string.IsNullOrEmpty(itemNo))
|
|
return $"{drawingNumber} PT{paddedItem}.dxf";
|
|
return $"PT{paddedItem}.dxf";
|
|
}
|
|
}
|
|
}
|