using claude to spice things up
This commit is contained in:
@@ -1,13 +0,0 @@
|
|||||||
package org.zaine.app.controller;
|
|
||||||
|
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
|
|
||||||
@RestController
|
|
||||||
public class HealthController {
|
|
||||||
|
|
||||||
@GetMapping("/api/health")
|
|
||||||
public String health() {
|
|
||||||
return "OK";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -7,30 +7,46 @@ import org.springframework.web.bind.annotation.*;
|
|||||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.RandomAccessFile;
|
import java.io.RandomAccessFile;
|
||||||
|
import java.lang.management.ManagementFactory;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api")
|
@RequestMapping("/api")
|
||||||
|
|
||||||
public class BuildController {
|
public class BuildController {
|
||||||
|
|
||||||
// Separate state for each build
|
// Separate state for each build
|
||||||
private final AtomicBoolean webBuildRunning = new AtomicBoolean(false);
|
private final AtomicBoolean webBuildRunning = new AtomicBoolean(false);
|
||||||
private final AtomicBoolean roamBuildRunning = new AtomicBoolean(false);
|
private final AtomicBoolean roamBuildRunning = new AtomicBoolean(false);
|
||||||
|
private final AtomicBoolean emacsRunning = new AtomicBoolean(false);
|
||||||
|
private final AtomicBoolean combinedRunning = new AtomicBoolean(false);
|
||||||
|
|
||||||
|
// Cancellable process references
|
||||||
|
private final AtomicReference<Process> webProcess = new AtomicReference<>();
|
||||||
|
private final AtomicReference<Process> roamProcess = new AtomicReference<>();
|
||||||
|
private final AtomicReference<Process> emacsProcess = new AtomicReference<>();
|
||||||
|
private final AtomicReference<Process> combinedProcess = new AtomicReference<>();
|
||||||
|
|
||||||
|
private volatile Instant emacsLastRun;
|
||||||
|
private volatile Integer emacsLastExitCode;
|
||||||
private volatile Instant webLastRun;
|
private volatile Instant webLastRun;
|
||||||
private volatile Integer webLastExitCode;
|
private volatile Integer webLastExitCode;
|
||||||
|
|
||||||
private volatile Instant roamLastRun;
|
private volatile Instant roamLastRun;
|
||||||
private volatile Integer roamLastExitCode;
|
private volatile Integer roamLastExitCode;
|
||||||
|
private volatile Instant combinedLastRun;
|
||||||
|
private volatile Integer combinedLastExitCode;
|
||||||
|
|
||||||
private static final Logger log =
|
private static final Logger log = LoggerFactory.getLogger(BuildController.class);
|
||||||
LoggerFactory.getLogger(BuildController.class);
|
|
||||||
|
// Server start time for uptime calculation
|
||||||
|
private static final Instant SERVER_START = Instant.now();
|
||||||
|
|
||||||
@Value("${zone.build.dir}")
|
@Value("${zone.build.dir}")
|
||||||
private String webBuildDirectory;
|
private String webBuildDirectory;
|
||||||
@@ -44,29 +60,52 @@ public class BuildController {
|
|||||||
@Value("${orgroam.build.log}")
|
@Value("${orgroam.build.log}")
|
||||||
private String roamBuildLogFile;
|
private String roamBuildLogFile;
|
||||||
|
|
||||||
|
@Value("${emacs.run.dir}")
|
||||||
|
private String emacsRunDirectory;
|
||||||
|
|
||||||
|
@Value("${emacs.run.log}")
|
||||||
|
private String emacsRunLogFile;
|
||||||
|
|
||||||
|
@Value("${combined.run.log}")
|
||||||
|
private String combinedRunLogFile;
|
||||||
|
|
||||||
|
/* =========================================================
|
||||||
|
HEALTH + UPTIME
|
||||||
|
========================================================= */
|
||||||
|
|
||||||
|
@GetMapping("/health")
|
||||||
|
public ResponseEntity<String> health() {
|
||||||
|
return ResponseEntity.ok("ok");
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/uptime")
|
||||||
|
public ResponseEntity<Map<String, Object>> getUptime() {
|
||||||
|
long uptimeMs = Instant.now().toEpochMilli() - SERVER_START.toEpochMilli();
|
||||||
|
long jvmUptimeMs = ManagementFactory.getRuntimeMXBean().getUptime();
|
||||||
|
return ResponseEntity.ok(Map.of(
|
||||||
|
"serverStart", SERVER_START,
|
||||||
|
"uptimeMs", uptimeMs,
|
||||||
|
"jvmUptimeMs", jvmUptimeMs
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
/* =========================================================
|
/* =========================================================
|
||||||
WEB BUILD
|
WEB BUILD
|
||||||
========================================================= */
|
========================================================= */
|
||||||
|
|
||||||
@PostMapping("/build-web")
|
@PostMapping("/build-web")
|
||||||
public ResponseEntity<String> triggerWebBuild() {
|
public ResponseEntity<String> triggerWebBuild() {
|
||||||
return startBuild(
|
return startBuild(webBuildDirectory, webBuildLogFile, webBuildRunning, webProcess, true);
|
||||||
webBuildDirectory,
|
}
|
||||||
webBuildLogFile,
|
|
||||||
webBuildRunning,
|
@DeleteMapping("/build-web")
|
||||||
true
|
public ResponseEntity<String> cancelWebBuild() {
|
||||||
);
|
return killProcess(webProcess, webBuildRunning, "web build");
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/build-web/status")
|
@GetMapping("/build-web/status")
|
||||||
public ResponseEntity<BuildStatus> getWebBuildStatus() {
|
public ResponseEntity<BuildStatus> getWebBuildStatus() {
|
||||||
return ResponseEntity.ok(
|
return ResponseEntity.ok(new BuildStatus(webBuildRunning.get(), webLastRun, webLastExitCode));
|
||||||
new BuildStatus(
|
|
||||||
webBuildRunning.get(),
|
|
||||||
webLastRun,
|
|
||||||
webLastExitCode
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/build-web/logs")
|
@GetMapping("/build-web/logs")
|
||||||
@@ -80,23 +119,17 @@ public class BuildController {
|
|||||||
|
|
||||||
@PostMapping("/build-roam")
|
@PostMapping("/build-roam")
|
||||||
public ResponseEntity<String> triggerRoamBuild() {
|
public ResponseEntity<String> triggerRoamBuild() {
|
||||||
return startBuild(
|
return startBuild(roamBuildDirectory, roamBuildLogFile, roamBuildRunning, roamProcess, false);
|
||||||
roamBuildDirectory,
|
}
|
||||||
roamBuildLogFile,
|
|
||||||
roamBuildRunning,
|
@DeleteMapping("/build-roam")
|
||||||
false
|
public ResponseEntity<String> cancelRoamBuild() {
|
||||||
);
|
return killProcess(roamProcess, roamBuildRunning, "roam build");
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/build-roam/status")
|
@GetMapping("/build-roam/status")
|
||||||
public ResponseEntity<BuildStatus> getRoamBuildStatus() {
|
public ResponseEntity<BuildStatus> getRoamBuildStatus() {
|
||||||
return ResponseEntity.ok(
|
return ResponseEntity.ok(new BuildStatus(roamBuildRunning.get(), roamLastRun, roamLastExitCode));
|
||||||
new BuildStatus(
|
|
||||||
roamBuildRunning.get(),
|
|
||||||
roamLastRun,
|
|
||||||
roamLastExitCode
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/build-roam/logs")
|
@GetMapping("/build-roam/logs")
|
||||||
@@ -104,68 +137,174 @@ public class BuildController {
|
|||||||
return streamLogs(roamBuildLogFile);
|
return streamLogs(roamBuildLogFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* =========================================================
|
||||||
|
EMACS RERUN
|
||||||
|
========================================================= */
|
||||||
|
|
||||||
|
@PostMapping("/rerun-emacs")
|
||||||
|
public ResponseEntity<String> triggerEmacs() {
|
||||||
|
log.info("Triggering Emacs command");
|
||||||
|
return startCommand(emacsRunDirectory, emacsRunLogFile, emacsRunning, emacsProcess);
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping("/rerun-emacs")
|
||||||
|
public ResponseEntity<String> cancelEmacs() {
|
||||||
|
return killProcess(emacsProcess, emacsRunning, "emacs");
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/rerun-emacs/status")
|
||||||
|
public ResponseEntity<BuildStatus> getEmacsStatus() {
|
||||||
|
return ResponseEntity.ok(new BuildStatus(emacsRunning.get(), emacsLastRun, emacsLastExitCode));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/rerun-emacs/logs")
|
||||||
|
public SseEmitter streamEmacsLogs() {
|
||||||
|
return streamLogs(emacsRunLogFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =========================================================
|
||||||
|
COMBINED: EMACS + ROAM (sequential)
|
||||||
|
========================================================= */
|
||||||
|
|
||||||
|
@PostMapping("/run-combined")
|
||||||
|
public ResponseEntity<String> triggerCombined() {
|
||||||
|
if (!combinedRunning.compareAndSet(false, true)) {
|
||||||
|
return ResponseEntity.status(HttpStatus.CONFLICT).body("Combined run already in progress");
|
||||||
|
}
|
||||||
|
if (emacsRunning.get() || roamBuildRunning.get()) {
|
||||||
|
combinedRunning.set(false);
|
||||||
|
return ResponseEntity.status(HttpStatus.CONFLICT).body("Emacs or Roam already running independently");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
File logFile = prepareLogFile(combinedRunLogFile);
|
||||||
|
combinedLastRun = Instant.now();
|
||||||
|
|
||||||
|
new Thread(() -> {
|
||||||
|
try {
|
||||||
|
appendToLog(logFile, "=== [1/2] Starting Emacs ===\n");
|
||||||
|
|
||||||
|
// Step 1: Emacs
|
||||||
|
ProcessBuilder emacsPb = new ProcessBuilder(
|
||||||
|
"bash", "-c",
|
||||||
|
"TERM=vt100 /usr/bin/timeout 10 /usr/bin/script -q -c \"emacs -nw\" /dev/null"
|
||||||
|
);
|
||||||
|
emacsPb.directory(Path.of(emacsRunDirectory).toFile());
|
||||||
|
emacsPb.redirectErrorStream(true);
|
||||||
|
emacsPb.redirectOutput(ProcessBuilder.Redirect.appendTo(logFile));
|
||||||
|
|
||||||
|
Process ep = emacsPb.start();
|
||||||
|
combinedProcess.set(ep);
|
||||||
|
int emacsExit = ep.waitFor();
|
||||||
|
emacsLastExitCode = emacsExit;
|
||||||
|
|
||||||
|
appendToLog(logFile, "\n=== Emacs exited (code " + emacsExit + ") ===\n");
|
||||||
|
appendToLog(logFile, "=== [2/2] Starting Roam build ===\n");
|
||||||
|
|
||||||
|
// Step 2: Roam make
|
||||||
|
ProcessBuilder roamPb = new ProcessBuilder("make");
|
||||||
|
roamPb.directory(Path.of(roamBuildDirectory).toFile());
|
||||||
|
roamPb.redirectErrorStream(true);
|
||||||
|
roamPb.redirectOutput(ProcessBuilder.Redirect.appendTo(logFile));
|
||||||
|
|
||||||
|
Process rp = roamPb.start();
|
||||||
|
combinedProcess.set(rp);
|
||||||
|
int roamExit = rp.waitFor();
|
||||||
|
roamLastExitCode = roamExit;
|
||||||
|
roamLastRun = Instant.now();
|
||||||
|
|
||||||
|
int finalCode = (emacsExit == 0 || emacsExit == 124) && roamExit == 0 ? 0 : 1;
|
||||||
|
combinedLastExitCode = finalCode;
|
||||||
|
|
||||||
|
appendToLog(logFile, "\n=== Roam build exited (code " + roamExit + ") ===\n");
|
||||||
|
appendToLog(logFile, "=== Combined run complete (exit " + finalCode + ") ===\n");
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
combinedLastExitCode = 1;
|
||||||
|
log.error("Combined run failed", e);
|
||||||
|
} finally {
|
||||||
|
combinedRunning.set(false);
|
||||||
|
combinedProcess.set(null);
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
|
||||||
|
return ResponseEntity.ok("Combined run started");
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
combinedRunning.set(false);
|
||||||
|
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||||
|
.body("Failed to start: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping("/run-combined")
|
||||||
|
public ResponseEntity<String> cancelCombined() {
|
||||||
|
return killProcess(combinedProcess, combinedRunning, "combined run");
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/run-combined/status")
|
||||||
|
public ResponseEntity<BuildStatus> getCombinedStatus() {
|
||||||
|
return ResponseEntity.ok(new BuildStatus(combinedRunning.get(), combinedLastRun, combinedLastExitCode));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/run-combined/logs")
|
||||||
|
public SseEmitter streamCombinedLogs() {
|
||||||
|
return streamLogs(combinedRunLogFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =========================================================
|
||||||
|
LAST RUN TIMESTAMPS (all at once)
|
||||||
|
========================================================= */
|
||||||
|
|
||||||
|
@GetMapping("/last-runs")
|
||||||
|
public ResponseEntity<Map<String, Object>> getLastRuns() {
|
||||||
|
return ResponseEntity.ok(Map.of(
|
||||||
|
"web", Map.of("lastRun", nullSafe(webLastRun), "exitCode", nullSafe(webLastExitCode)),
|
||||||
|
"roam", Map.of("lastRun", nullSafe(roamLastRun), "exitCode", nullSafe(roamLastExitCode)),
|
||||||
|
"emacs", Map.of("lastRun", nullSafe(emacsLastRun), "exitCode", nullSafe(emacsLastExitCode)),
|
||||||
|
"combined", Map.of("lastRun", nullSafe(combinedLastRun), "exitCode", nullSafe(combinedLastExitCode))
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
/* =========================================================
|
/* =========================================================
|
||||||
CORE BUILD LOGIC
|
CORE BUILD LOGIC
|
||||||
========================================================= */
|
========================================================= */
|
||||||
|
|
||||||
private ResponseEntity<String> startBuild(
|
private ResponseEntity<String> startBuild(
|
||||||
String buildDir,
|
String buildDir, String logPath,
|
||||||
String logPath,
|
AtomicBoolean runningFlag, AtomicReference<Process> processRef,
|
||||||
AtomicBoolean runningFlag,
|
boolean isWeb) {
|
||||||
boolean isWeb
|
|
||||||
) {
|
|
||||||
|
|
||||||
if (!runningFlag.compareAndSet(false, true)) {
|
if (!runningFlag.compareAndSet(false, true)) {
|
||||||
return ResponseEntity.status(HttpStatus.CONFLICT)
|
return ResponseEntity.status(HttpStatus.CONFLICT).body("Build already running");
|
||||||
.body("Build already running");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
File workingDir = Path.of(buildDir).toFile();
|
File logFile = prepareLogFile(logPath);
|
||||||
File logFile = new File(logPath);
|
|
||||||
|
|
||||||
if (logFile.getParentFile() != null) {
|
|
||||||
logFile.getParentFile().mkdirs();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (logFile.exists()) {
|
|
||||||
logFile.delete();
|
|
||||||
}
|
|
||||||
|
|
||||||
logFile.createNewFile();
|
|
||||||
|
|
||||||
ProcessBuilder pb = new ProcessBuilder("make");
|
ProcessBuilder pb = new ProcessBuilder("make");
|
||||||
pb.directory(workingDir);
|
pb.directory(Path.of(buildDir).toFile());
|
||||||
pb.redirectErrorStream(true);
|
pb.redirectErrorStream(true);
|
||||||
pb.redirectOutput(ProcessBuilder.Redirect.appendTo(logFile));
|
pb.redirectOutput(ProcessBuilder.Redirect.appendTo(logFile));
|
||||||
|
|
||||||
log.info("Starting build in {}", workingDir.getAbsolutePath());
|
log.info("Starting build in {}", buildDir);
|
||||||
|
|
||||||
Process process = pb.start();
|
Process process = pb.start();
|
||||||
|
processRef.set(process);
|
||||||
|
|
||||||
if (isWeb) {
|
if (isWeb) webLastRun = Instant.now();
|
||||||
webLastRun = Instant.now();
|
else roamLastRun = Instant.now();
|
||||||
} else {
|
|
||||||
roamLastRun = Instant.now();
|
|
||||||
}
|
|
||||||
|
|
||||||
new Thread(() -> {
|
new Thread(() -> {
|
||||||
try {
|
try {
|
||||||
int exit = process.waitFor();
|
int exit = process.waitFor();
|
||||||
|
if (isWeb) webLastExitCode = exit;
|
||||||
if (isWeb) {
|
else roamLastExitCode = exit;
|
||||||
webLastExitCode = exit;
|
|
||||||
} else {
|
|
||||||
roamLastExitCode = exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
log.info("Build finished with exit code {}", exit);
|
log.info("Build finished with exit code {}", exit);
|
||||||
|
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
Thread.currentThread().interrupt();
|
Thread.currentThread().interrupt();
|
||||||
log.error("Build thread interrupted", e);
|
log.error("Build thread interrupted", e);
|
||||||
} finally {
|
} finally {
|
||||||
runningFlag.set(false);
|
runningFlag.set(false);
|
||||||
|
processRef.set(null);
|
||||||
}
|
}
|
||||||
}).start();
|
}).start();
|
||||||
|
|
||||||
@@ -175,50 +314,122 @@ public class BuildController {
|
|||||||
runningFlag.set(false);
|
runningFlag.set(false);
|
||||||
log.error("Build failed to start", e);
|
log.error("Build failed to start", e);
|
||||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||||
.body("Failed to start build: " + e.getMessage());
|
.body("Failed to start build: " + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ResponseEntity<String> startCommand(
|
||||||
|
String runDir, String logPath,
|
||||||
|
AtomicBoolean runningFlag, AtomicReference<Process> processRef) {
|
||||||
|
|
||||||
|
if (!runningFlag.compareAndSet(false, true)) {
|
||||||
|
return ResponseEntity.status(HttpStatus.CONFLICT).body("Command already running");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
File logFile = prepareLogFile(logPath);
|
||||||
|
ProcessBuilder pb = new ProcessBuilder(
|
||||||
|
"bash", "-c",
|
||||||
|
"TERM=vt100 /usr/bin/timeout 10 /usr/bin/script -q -c \"emacs -nw\" /dev/null"
|
||||||
|
);
|
||||||
|
pb.directory(Path.of(runDir).toFile());
|
||||||
|
pb.redirectErrorStream(true);
|
||||||
|
pb.redirectOutput(ProcessBuilder.Redirect.appendTo(logFile));
|
||||||
|
|
||||||
|
Process process = pb.start();
|
||||||
|
processRef.set(process);
|
||||||
|
emacsLastRun = Instant.now();
|
||||||
|
|
||||||
|
new Thread(() -> {
|
||||||
|
try {
|
||||||
|
int exit = process.waitFor();
|
||||||
|
emacsLastExitCode = exit;
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
} finally {
|
||||||
|
runningFlag.set(false);
|
||||||
|
processRef.set(null);
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
|
||||||
|
return ResponseEntity.ok("Command started");
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
runningFlag.set(false);
|
||||||
|
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||||
|
.body("Failed to start command: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ResponseEntity<String> killProcess(
|
||||||
|
AtomicReference<Process> processRef,
|
||||||
|
AtomicBoolean runningFlag,
|
||||||
|
String label) {
|
||||||
|
|
||||||
|
Process p = processRef.get();
|
||||||
|
if (p == null || !p.isAlive()) {
|
||||||
|
return ResponseEntity.status(HttpStatus.NOT_FOUND).body("No running " + label);
|
||||||
|
}
|
||||||
|
p.destroyForcibly();
|
||||||
|
runningFlag.set(false);
|
||||||
|
processRef.set(null);
|
||||||
|
log.info("Killed {}", label);
|
||||||
|
return ResponseEntity.ok("Killed " + label);
|
||||||
|
}
|
||||||
|
|
||||||
/* =========================================================
|
/* =========================================================
|
||||||
LOG STREAMING (SSE)
|
LOG STREAMING (SSE)
|
||||||
========================================================= */
|
========================================================= */
|
||||||
|
|
||||||
private SseEmitter streamLogs(String logFilePath) {
|
private SseEmitter streamLogs(String logFilePath) {
|
||||||
|
|
||||||
SseEmitter emitter = new SseEmitter(0L);
|
SseEmitter emitter = new SseEmitter(0L);
|
||||||
|
|
||||||
new Thread(() -> {
|
new Thread(() -> {
|
||||||
try (RandomAccessFile file = new RandomAccessFile(logFilePath, "r")) {
|
try (RandomAccessFile file = new RandomAccessFile(logFilePath, "r")) {
|
||||||
|
|
||||||
long pointer = 0;
|
long pointer = 0;
|
||||||
|
|
||||||
while (!Thread.currentThread().isInterrupted()) {
|
while (!Thread.currentThread().isInterrupted()) {
|
||||||
|
|
||||||
long length = file.length();
|
long length = file.length();
|
||||||
|
|
||||||
if (length > pointer) {
|
if (length > pointer) {
|
||||||
file.seek(pointer);
|
file.seek(pointer);
|
||||||
|
|
||||||
String line;
|
String line;
|
||||||
while ((line = file.readLine()) != null) {
|
while ((line = file.readLine()) != null) {
|
||||||
emitter.send(SseEmitter.event().data(line));
|
emitter.send(SseEmitter.event().data(line));
|
||||||
}
|
}
|
||||||
|
|
||||||
pointer = file.getFilePointer();
|
pointer = file.getFilePointer();
|
||||||
}
|
}
|
||||||
|
|
||||||
Thread.sleep(1000);
|
Thread.sleep(1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.info("Log stream closed");
|
log.info("Log stream closed");
|
||||||
emitter.complete();
|
emitter.complete();
|
||||||
}
|
}
|
||||||
}).start();
|
}).start();
|
||||||
|
|
||||||
return emitter;
|
return emitter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* =========================================================
|
||||||
|
HELPERS
|
||||||
|
========================================================= */
|
||||||
|
|
||||||
|
private File prepareLogFile(String logPath) throws IOException {
|
||||||
|
File logFile = new File(logPath);
|
||||||
|
if (logFile.getParentFile() != null) logFile.getParentFile().mkdirs();
|
||||||
|
if (logFile.exists()) logFile.delete();
|
||||||
|
logFile.createNewFile();
|
||||||
|
return logFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void appendToLog(File logFile, String text) {
|
||||||
|
try (java.io.FileWriter fw = new java.io.FileWriter(logFile, true)) {
|
||||||
|
fw.write(text);
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.warn("Could not append to log", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object nullSafe(Object val) {
|
||||||
|
return val != null ? val : "never";
|
||||||
|
}
|
||||||
|
|
||||||
/* =========================================================
|
/* =========================================================
|
||||||
DTO
|
DTO
|
||||||
========================================================= */
|
========================================================= */
|
||||||
@@ -229,8 +440,8 @@ public class BuildController {
|
|||||||
public Integer lastExitCode;
|
public Integer lastExitCode;
|
||||||
|
|
||||||
public BuildStatus(boolean running, Instant lastRun, Integer lastExitCode) {
|
public BuildStatus(boolean running, Instant lastRun, Integer lastExitCode) {
|
||||||
this.running = running;
|
this.running = running;
|
||||||
this.lastRun = lastRun;
|
this.lastRun = lastRun;
|
||||||
this.lastExitCode = lastExitCode;
|
this.lastExitCode = lastExitCode;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user