mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-11-04 05:51:34 -05:00
A server Auto Nest result could only be reviewed in the session that sent it, and nothing stopped two desktops from keeping the same result. Phase 3 adds My Server Jobs, so results that finished while OpenNest was closed are easy to find, and records each review on the server. - Server: GET /api/nest-jobs lists jobs newest first with state, engine, submitter, times, queue position, review and the worker's summary (parts, sheets, utilization, unplaced). It filters to active, finished or unreviewed jobs and to a submitter (a bare user name matches that user on any workstation). An ETag turns unchanged polls into 304s. Schema v17 adds nestId, review and summary columns, only where missing. - Keep once: POST .../keep and .../discard take If-Match on the job revision and change an unreviewed result only once. A second desktop gets 409 with who kept it. DELETE removes finished jobs. A succeeded job keeps its package until it is reviewed, so a later session can rebuild the job. - Retention: OPENNEST_JOB_RETENTION_DAYS (default 7) deletes finished jobs with their results. It runs on claims and list reads. A malformed value fails startup instead of deleting results early. - Worker: reports the result's summary with the transcript. A job cancelled while its result arrived keeps no summary (found in the Docker smoke run). - Desktop: Nest > My Server Jobs polls the list every 3 s; polling rather than per-job SSE because the list must show other desktops' jobs and removals. Open rebuilds the job from job.json against the open nest's own drawings. New per-drawing geometry fingerprints make the freshness check work across sessions. Cancel Job and Remove act on the selected job. A status-bar item counts this user's results waiting for review; there is no popup. Auto Nest's review now records Keep on the server before it commits, and records Discard too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
230 lines
8.1 KiB
C#
230 lines
8.1 KiB
C#
namespace OpenNest.Data;
|
|
|
|
/// <summary>Where a server nest job is. Succeeded, Failed and Cancelled are final.</summary>
|
|
public enum NestJobState
|
|
{
|
|
Queued,
|
|
Running,
|
|
Succeeded,
|
|
Failed,
|
|
Cancelled,
|
|
}
|
|
|
|
/// <summary>A whole-job nest the server's worker solves; see <c>docs/server-nesting.md</c>.</summary>
|
|
public sealed class NestJobRecord
|
|
{
|
|
public Guid Id { get; set; }
|
|
|
|
/// <summary>What the job is for, for people: usually the nest's name.</summary>
|
|
public string Title { get; set; } = "";
|
|
|
|
public string EngineName { get; set; } = "";
|
|
|
|
public string SubmittedBy { get; set; } = "";
|
|
|
|
public NestJobState State { get; set; }
|
|
|
|
/// <summary>Someone asked to stop the job while it ran; the worker stops at its next heartbeat.</summary>
|
|
public bool CancelRequested { get; set; }
|
|
|
|
public DateTime SubmittedAt { get; set; }
|
|
|
|
public DateTime? StartedAt { get; set; }
|
|
|
|
public DateTime? FinishedAt { get; set; }
|
|
|
|
/// <summary>How many times a worker has taken the job; a worker that stops responding gives it back.</summary>
|
|
public int Attempts { get; set; }
|
|
|
|
/// <summary>Why the job failed; empty otherwise.</summary>
|
|
public string Error { get; set; } = "";
|
|
|
|
/// <summary>Queued jobs ahead of this one (zero for a job that is not queued).</summary>
|
|
public int QueuePosition { get; set; }
|
|
|
|
public long PackageSize { get; set; }
|
|
|
|
/// <summary>
|
|
/// Bumped by every change a watcher shows (state, cancel request, progress, plan); the
|
|
/// event stream's ids. Heartbeats leave it alone.
|
|
/// </summary>
|
|
public long Revision { get; set; }
|
|
|
|
/// <summary>The <see cref="Revision"/> the best plan so far was stored at; zero when there is none.</summary>
|
|
public long PlanRevision { get; set; }
|
|
|
|
/// <summary>
|
|
/// The worker's latest progress report while the job runs (<c>RemoteNestProgress</c> JSON,
|
|
/// opaque to the server); empty when there is none.
|
|
/// </summary>
|
|
public string Progress { get; set; } = "";
|
|
|
|
/// <summary>The server nest the job was sent from, when it had been saved there; null otherwise.</summary>
|
|
public Guid? NestId { get; set; }
|
|
|
|
/// <summary>Whether a succeeded job's result was kept or discarded; each happens at most once.</summary>
|
|
public NestJobReview Review { get; set; }
|
|
|
|
/// <summary>Who kept or discarded the result; empty while it is unreviewed.</summary>
|
|
public string ReviewedBy { get; set; } = "";
|
|
|
|
public DateTime? ReviewedAt { get; set; }
|
|
|
|
/// <summary>What a succeeded job's result holds, as its worker reported it; null for other jobs and older workers.</summary>
|
|
public NestJobSummary? Summary { get; set; }
|
|
|
|
public bool IsFinished => State is NestJobState.Succeeded or NestJobState.Failed or NestJobState.Cancelled;
|
|
|
|
/// <summary>A succeeded job nobody has kept or discarded yet: its result can still be opened.</summary>
|
|
public bool IsUnreviewed => State == NestJobState.Succeeded && Review == NestJobReview.None;
|
|
}
|
|
|
|
/// <summary>What became of a succeeded job's result. Kept and Discarded are final.</summary>
|
|
public enum NestJobReview
|
|
{
|
|
None,
|
|
Kept,
|
|
Discarded,
|
|
}
|
|
|
|
/// <summary>A finished job's result in numbers, for job lists (the server never opens the result).</summary>
|
|
public sealed class NestJobSummary
|
|
{
|
|
/// <summary>Parts placed, counting every copy of a repeated sheet.</summary>
|
|
public int PartsPlaced { get; set; }
|
|
|
|
public int PartsUnplaced { get; set; }
|
|
|
|
/// <summary>Physical sheets, counting repeats.</summary>
|
|
public int Sheets { get; set; }
|
|
|
|
/// <summary>Part area over sheet area across every sheet, 0 to 1.</summary>
|
|
public double Utilization { get; set; }
|
|
}
|
|
|
|
/// <summary>Which jobs <c>GET /api/nest-jobs</c> lists.</summary>
|
|
public enum NestJobListFilter
|
|
{
|
|
All,
|
|
|
|
/// <summary>Queued or running.</summary>
|
|
Active,
|
|
|
|
/// <summary>Succeeded, failed or cancelled.</summary>
|
|
Finished,
|
|
|
|
/// <summary>Succeeded and neither kept nor discarded.</summary>
|
|
Unreviewed,
|
|
}
|
|
|
|
/// <summary>The description sent with a job's package.</summary>
|
|
public sealed class NestJobSubmission
|
|
{
|
|
public string Title { get; set; } = "";
|
|
|
|
public string EngineName { get; set; } = "";
|
|
|
|
public string SubmittedBy { get; set; } = "";
|
|
|
|
/// <summary>The server nest the job is for, when it is saved there (helps a later review find it).</summary>
|
|
public Guid? NestId { get; set; }
|
|
}
|
|
|
|
/// <summary>A desktop's Keep or Discard of a succeeded job's result.</summary>
|
|
public sealed class NestJobReviewRequest
|
|
{
|
|
public string ReviewedBy { get; set; } = "";
|
|
}
|
|
|
|
/// <summary>A worker's message about the job it holds.</summary>
|
|
public sealed class NestJobWorkerMessage
|
|
{
|
|
public string WorkerId { get; set; } = "";
|
|
|
|
/// <summary>Why the job failed, for <c>fail</c>; ignored otherwise.</summary>
|
|
public string Error { get; set; } = "";
|
|
}
|
|
|
|
/// <summary>The server's answer to a worker's heartbeat.</summary>
|
|
public sealed class NestJobHeartbeat
|
|
{
|
|
public bool CancelRequested { get; set; }
|
|
}
|
|
|
|
/// <summary>What a job's event stream (<c>GET /api/nest-jobs/{id}/events</c>) sent.</summary>
|
|
public enum NestJobEventKind
|
|
{
|
|
/// <summary>The stream is open; nothing has necessarily changed.</summary>
|
|
Open,
|
|
|
|
/// <summary>The job itself: <see cref="NestJobEvent.Job"/>. The last one of a stream is final.</summary>
|
|
Job,
|
|
|
|
/// <summary>A new best plan so far: <see cref="NestJobEvent.Plan"/>, sent before the job event that names it.</summary>
|
|
Plan,
|
|
|
|
/// <summary>The server no longer holds the job.</summary>
|
|
Gone,
|
|
}
|
|
|
|
public sealed class NestJobEvent
|
|
{
|
|
public NestJobEventKind Kind { get; init; }
|
|
|
|
/// <summary>The event id: the job's <see cref="NestJobRecord.Revision"/>, or the plan's for a plan.</summary>
|
|
public long Id { get; init; }
|
|
|
|
public NestJobRecord? Job { get; init; }
|
|
|
|
/// <summary>The worker's plan (<c>RemoteNestPlan</c> JSON, opaque to the server).</summary>
|
|
public string Plan { get; init; } = "";
|
|
}
|
|
|
|
/// <summary>Limits and timings both sides of server nesting share.</summary>
|
|
public static class NestJobRules
|
|
{
|
|
/// <summary>Largest job package (drawings and job) the server accepts.</summary>
|
|
public const long MaxPackageBytes = 256L * 1024 * 1024;
|
|
|
|
/// <summary>Largest result the server accepts from a worker.</summary>
|
|
public const long MaxResultBytes = 64L * 1024 * 1024;
|
|
|
|
public const int MaxTextLength = 500;
|
|
|
|
/// <summary>How long a running job stays with its worker without a heartbeat.</summary>
|
|
public static readonly TimeSpan Lease = TimeSpan.FromMinutes(2);
|
|
|
|
/// <summary>How often a worker reports; well inside <see cref="Lease"/>.</summary>
|
|
public static readonly TimeSpan HeartbeatInterval = TimeSpan.FromSeconds(20);
|
|
|
|
/// <summary>Largest progress report a worker may store.</summary>
|
|
public const long MaxProgressBytes = 64L * 1024;
|
|
|
|
/// <summary>Largest best-plan-so-far a worker may store.</summary>
|
|
public const long MaxPlanBytes = 16L * 1024 * 1024;
|
|
|
|
/// <summary>
|
|
/// How often a worker at most stores progress and plans (only the latest of each is sent),
|
|
/// and checks for a cancel request between reports.
|
|
/// </summary>
|
|
public static readonly TimeSpan ProgressInterval = TimeSpan.FromSeconds(2);
|
|
|
|
/// <summary>How often an idle event stream sends a comment, so a dropped connection shows.</summary>
|
|
public static readonly TimeSpan StreamKeepAlive = TimeSpan.FromSeconds(15);
|
|
|
|
/// <summary>A stream silent this long (several missed keep-alives) is treated as dropped.</summary>
|
|
public static readonly TimeSpan StreamIdleTimeout = TimeSpan.FromSeconds(45);
|
|
|
|
/// <summary>Workers that stop responding this many times fail the job instead of retrying it.</summary>
|
|
public const int MaxAttempts = 3;
|
|
|
|
/// <summary>
|
|
/// Finished jobs, their packages and results are removed this long after they finish,
|
|
/// unless the server is configured otherwise (<c>OPENNEST_JOB_RETENTION_DAYS</c>).
|
|
/// </summary>
|
|
public static readonly TimeSpan Retention = TimeSpan.FromDays(7);
|
|
|
|
/// <summary>Most jobs one list request returns.</summary>
|
|
public const int MaxListLimit = 500;
|
|
}
|