diff --git a/.gitignore b/.gitignore index dd873048..85b11fa2 100644 --- a/.gitignore +++ b/.gitignore @@ -35,4 +35,6 @@ scripts/freeroutingp scripts/logs scripts/test.dsn scripts/test.ses -scripts/freeroutingp.exe \ No newline at end of file +scripts/freeroutingp.exe +.aider* +.env diff --git a/src/main/java/app/freerouting/api/v1/JobControllerV1.java b/src/main/java/app/freerouting/api/v1/JobControllerV1.java index 8e799dd6..e9977539 100644 --- a/src/main/java/app/freerouting/api/v1/JobControllerV1.java +++ b/src/main/java/app/freerouting/api/v1/JobControllerV1.java @@ -340,7 +340,10 @@ public Response uploadInput( @Produces(MediaType.APPLICATION_JSON) public Response downloadOutput( @PathParam("jobId") - String jobId) + String jobId, + @QueryParam("speculative") + @DefaultValue("false") + boolean speculative) { // Authenticate the user UUID userId = AuthenticateUser(); @@ -361,20 +364,46 @@ public Response downloadOutput( return Response.status(Response.Status.BAD_REQUEST).entity("{\"error\":\"The session ID '" + job.sessionId + "' is invalid.\"}").build(); } - // Check if the job is completed - if (job.state != RoutingJobState.COMPLETED) + // Check if we can return output + if (!speculative && job.state != RoutingJobState.COMPLETED) { return Response.status(Response.Status.BAD_REQUEST).entity("{\"error\":\"The job hasn't finished yet.\"}").build(); } + // For speculative results, only return if the job is running or completed + if (speculative && job.state != RoutingJobState.RUNNING && job.state != RoutingJobState.COMPLETED) + { + return Response.status(Response.Status.BAD_REQUEST).entity("{\"error\":\"The job must be running or completed to get speculative results.\"}").build(); + } + var result = new BoardFilePayload(); result.jobId = job.id; - result.setFilename(job.output.getFilename()); - result.setData(job.output.getData().readAllBytes()); - result.dataBase64 = java.util.Base64.getEncoder().encodeToString(result.getData().readAllBytes()); + result.setFilename(job.output != null ? job.output.getFilename() : job.input.getFilename().replace(".dsn", ".ses")); + + // For speculative results, get the current board state + if (speculative && job.state == RoutingJobState.RUNNING) + { + // Get the current board state as a session file + byte[] speculativeData = job.getSpeculativeOutput(); + if (speculativeData != null) + { + result.setData(speculativeData); + result.dataBase64 = java.util.Base64.getEncoder().encodeToString(speculativeData); + } + else + { + return Response.status(Response.Status.BAD_REQUEST).entity("{\"error\":\"No speculative results available yet.\"}").build(); + } + } + else + { + // Return the final output + result.setData(job.output.getData().readAllBytes()); + result.dataBase64 = java.util.Base64.getEncoder().encodeToString(result.getData().readAllBytes()); + } var response = GSON.toJson(result); - FRAnalytics.apiEndpointCalled("GET v1/jobs/" + jobId + "/output", "", response.replace(result.dataBase64, TextManager.shortenString(result.dataBase64, 4))); + FRAnalytics.apiEndpointCalled("GET v1/jobs/" + jobId + "/output" + (speculative ? "?speculative=true" : ""), "", response.replace(result.dataBase64, TextManager.shortenString(result.dataBase64, 4))); return Response.ok(response).build(); } @@ -411,4 +440,4 @@ public Response logs( FRAnalytics.apiEndpointCalled("GET v1/jobs/" + jobId + "/logs", "", response); return Response.ok(response).build(); } -} \ No newline at end of file +} diff --git a/src/main/java/app/freerouting/core/RoutingJob.java b/src/main/java/app/freerouting/core/RoutingJob.java index c8351cf9..1226b79e 100644 --- a/src/main/java/app/freerouting/core/RoutingJob.java +++ b/src/main/java/app/freerouting/core/RoutingJob.java @@ -15,7 +15,12 @@ import javax.swing.*; import javax.swing.filechooser.FileNameExtensionFilter; import java.awt.*; -import java.io.*; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.Serializable; import java.nio.file.Files; import java.nio.file.Path; import java.time.Instant; @@ -520,4 +525,19 @@ public void fireOutputUpdatedEvent() } } -} \ No newline at end of file + /** + * Gets the current board state as a session file. + * Used to provide intermediate routing results while the job is still running. + * @return The current board state as a byte array, or null if not available + */ + public byte[] getSpeculativeOutput() + { + // Only return speculative results if we have a board and are running + if (board == null || state != RoutingJobState.RUNNING) { + return null; + } + + // TODO + } + +}