feat(ui): wire file and database nest storage

This commit is contained in:
aj
2026-09-29 16:21:17 -04:00
parent 437d659c9d
commit 64045be968
10 changed files with 896 additions and 27 deletions
+45
View File
@@ -0,0 +1,45 @@
using System.Linq;
namespace OpenNest.Data;
/// <summary>
/// Builds the shareable <see cref="NestRecord"/> metadata from a live <see cref="Nest"/>.
/// Counts are computed here (not carried on <see cref="Nest"/> itself) so every caller
/// that saves to the nest server gets the same definition of "plate" and "part" counts.
/// </summary>
public static class NestRecordFactory
{
/// <summary>
/// Builds a record for uploading <paramref name="nest"/>. <paramref name="id"/> is the
/// client-tracked id: <see cref="System.Guid.Empty"/> for a first save (the server
/// assigns one), or the previously returned id to update an existing record.
/// <paramref name="fileSize"/> is the size of the serialized .nest archive being
/// uploaded alongside this record; the server also recomputes it independently.
/// </summary>
public static NestRecord FromNest(Nest nest, System.Guid id, long fileSize)
{
ArgumentNullException.ThrowIfNull(nest);
return new NestRecord
{
Id = id,
Name = nest.Name ?? "",
Customer = nest.Customer ?? "",
DateCreated = nest.DateCreated,
DateModified = nest.DateLastModified,
Material = nest.Material?.Name ?? "",
Thickness = nest.Thickness,
Status = nest.Status,
PlateCount = nest.Plates.Count,
PartCount = nest.Plates.Sum(CountNonCutoffParts),
// Nest has no separate "comments" field; Notes is the closest analog
// shown in the nest info dialog, so it round-trips as Comments here.
Comments = nest.Notes ?? "",
MadeBy = nest.MadeBy ?? "",
FileSize = fileSize,
};
}
private static int CountNonCutoffParts(Plate plate) =>
plate.Parts.Count(part => !part.BaseDrawing.IsCutOff);
}
+46
View File
@@ -0,0 +1,46 @@
namespace OpenNest.Data;
/// <summary>
/// Tracks a document's database identity, bound to a particular server address.
/// A failed upload leaves that identity untouched. Switching servers creates a new
/// record rather than accidentally updating an unrelated record with the same id.
/// </summary>
public sealed class NestSaveSession
{
public Guid RemoteId { get; private set; }
public string ServerUrl { get; private set; } = "";
public void Bind(Guid id, string serverUrl)
{
if (id == Guid.Empty)
throw new ArgumentException("A saved nest needs a non-empty id.", nameof(id));
RemoteId = id;
ServerUrl = Normalize(serverUrl);
}
public async Task<NestRecord> SaveAsync(
INestRepository repository, string serverUrl, Nest nest, byte[] archive,
bool saveCopy = false, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(repository);
ArgumentNullException.ThrowIfNull(nest);
ArgumentNullException.ThrowIfNull(archive);
var normalizedUrl = Normalize(serverUrl);
if (normalizedUrl.Length == 0)
throw new ArgumentException("A database server URL is required.", nameof(serverUrl));
var id = !saveCopy && ServerUrl == normalizedUrl ? RemoteId : Guid.Empty;
var record = NestRecordFactory.FromNest(nest, id, archive.LongLength);
var saved = id == Guid.Empty
? await repository.UploadAsync(archive, record, cancellationToken)
: await repository.UpdateFileAsync(id, archive, record, cancellationToken);
if (saved.Id == Guid.Empty || (id != Guid.Empty && saved.Id != id))
throw new InvalidDataException("Server returned an invalid nest id.");
Bind(saved.Id, normalizedUrl);
return saved;
}
private static string Normalize(string? url) => (url ?? "").Trim().TrimEnd('/');
}