mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-10-27 16:11:07 -04:00
Schema v5 stores each distinct drawing package once in drawing_packages, keyed by SHA-256; revisions point at it, so details, settings, archive and restore revisions add no blob. The v4 drawing tables are replaced outright (no production library existed); nest tables are untouched. Nesting settings (cut first, priority, rotation constraints, color) become structured record fields (DrawingLibraryRecord.Settings), returned on every read, filterable by cutFirst, editable through the metadata endpoint and applied by LibraryDrawingInsert. Canonical packages now leave every record-owned field at its default, so renaming or changing settings never changes the package hash. Saving a nest drawing to the library carries its settings into the record. The desktop library browser gains a Cut first column and an Edit Settings dialog that saves notes and settings as a new revision with the same package. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
75 lines
3.3 KiB
C#
75 lines
3.3 KiB
C#
using OpenNest;
|
|
|
|
namespace OpenNest.Data;
|
|
|
|
/// <summary>
|
|
/// Bounds shared by the drawing library client and server. The client checks them
|
|
/// before sending; the server refuses anything outside them with no state change.
|
|
/// </summary>
|
|
public static class DrawingLibraryRules
|
|
{
|
|
/// <summary>Longest name, customer, material, grade or saved-by text.</summary>
|
|
public const int MaxTextLength = 200;
|
|
|
|
public const int MaxNotesLength = 4000;
|
|
|
|
/// <summary>Largest accepted drawing package (16 MiB).</summary>
|
|
public const int MaxPackageBytes = 16 * 1024 * 1024;
|
|
|
|
public const string PackageTooLargeError = "The drawing package exceeds 16 MiB.";
|
|
|
|
public const int DefaultRevisionLimit = 100;
|
|
|
|
public const int MaxRevisionLimit = 200;
|
|
|
|
/// <summary>Thickness as stored and compared: rounded to four decimals.</summary>
|
|
public static double NormalizeThickness(double thickness) =>
|
|
System.Math.Round(thickness, 4, MidpointRounding.AwayFromZero);
|
|
|
|
/// <summary>The first metadata rule <paramref name="record"/> breaks, or null when it may be saved.</summary>
|
|
public static string? GetMetadataError(DrawingLibraryRecord? record)
|
|
{
|
|
if (record is null)
|
|
return "Missing metadata.";
|
|
if (string.IsNullOrWhiteSpace(record.Name))
|
|
return "name is required.";
|
|
return TextError("name", record.Name, MaxTextLength)
|
|
?? TextError("customer", record.Customer, MaxTextLength)
|
|
?? TextError("materialName", record.MaterialName, MaxTextLength)
|
|
?? TextError("materialGrade", record.MaterialGrade, MaxTextLength)
|
|
?? TextError("savedBy", record.SavedBy, MaxTextLength)
|
|
?? TextError("notes", record.Notes, MaxNotesLength)
|
|
?? (!double.IsFinite(record.Thickness) || record.Thickness < 0
|
|
? "thickness must be a finite number, zero or greater."
|
|
: null)
|
|
?? (!Enum.IsDefined(record.Units) ? "units must be a supported unit name." : null)
|
|
?? (!Enum.IsDefined(record.CutType) ? "cutType must be Laser, Waterjet or Plasma." : null)
|
|
?? (record.Settings is null ? "settings is required." : record.Settings.GetValidationError());
|
|
}
|
|
|
|
/// <summary>The first package rule <paramref name="package"/> breaks, or null when it may be stored.</summary>
|
|
public static string? GetPackageError(ReadOnlySpan<byte> package)
|
|
{
|
|
if (package.Length == 0)
|
|
return "The drawing package is empty.";
|
|
if (package.Length > MaxPackageBytes)
|
|
return PackageTooLargeError;
|
|
// Every ZIP archive, including an empty one, starts with a "PK" record signature.
|
|
if (package.Length < 4 || package[0] != (byte)'P' || package[1] != (byte)'K')
|
|
return "The drawing package is not a ZIP archive.";
|
|
return null;
|
|
}
|
|
|
|
internal static string? TextError(string field, string? value, int maxLength)
|
|
{
|
|
if (value is null)
|
|
return $"{field} is required.";
|
|
if (value.Length > maxLength)
|
|
return $"{field} must be at most {maxLength} characters.";
|
|
// SQLite LIKE stops at an embedded NUL; refuse it everywhere for one rule.
|
|
if (value.Contains('\0'))
|
|
return $"{field} must not contain NUL characters.";
|
|
return null;
|
|
}
|
|
}
|