Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

issue 102 -- fix race condition in status window #139

Merged
merged 2 commits into from
May 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,12 @@
import java.util.ResourceBundle;
import java.util.Set;

import javafx.application.Platform;
import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
Expand All @@ -39,7 +42,9 @@
import javafx.scene.control.TextField;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.util.Duration;
import javafx.util.StringConverter;
import org.tallison.tika.app.fx.batch.BatchProcess;
import org.tallison.tika.app.fx.ctx.AppContext;
Expand Down Expand Up @@ -71,7 +76,11 @@ public class BatchStatusController implements Initializable {

@FXML
private final ObservableList<StatusCount> statusCounts = FXCollections.observableArrayList();

private final Label pieSliceCaption = new Label("");

@FXML
Pane statusPane;
@FXML
PieChart statusPieChart;
@FXML
Expand All @@ -85,30 +94,73 @@ public class BatchStatusController implements Initializable {
@FXML
TableView statusTable;
ObservableList<PieChart.Data> pieChartData = FXCollections.observableArrayList();
private Thread updaterThread;

private final Timeline timeline;

public BatchStatusController() {
timeline = new Timeline(new KeyFrame(Duration.millis(100), new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent event) {
updateWindow();
}

}));
timeline.setCycleCount(Animation.INDEFINITE);
}

public ObservableList<StatusCount> getStatusCounts() {
return statusCounts;
}

@Override
public void initialize(URL fxmlFileLocation, ResourceBundle resources) {
//TODO: some of this can't be in the constructor because
//the objects are created at fxml load time
pieSliceCaption.setTextFill(Color.DARKORANGE);
pieSliceCaption.setStyle("-fx-font: 24 arial;");
pieSliceCaption.setStyle("-fx-font: 16 arial;");

statusTable.getSortOrder().add(countColumn);
countColumn.setSortType(TableColumn.SortType.DESCENDING);
countColumn.setCellFactory(
TextFieldTableCell.forTableColumn(new MyDoubleStringConverter()));
updaterThread = new Thread(new Updater());
updaterThread.setDaemon(true);
updaterThread.start();

statusPieChart.setLegendVisible(false);
statusPieChart.setData(pieChartData);
statusPieChart.setLabelsVisible(true);
statusPieChart.setAnimated(true);
overallStatus.setText("Running");
statusPane.getChildren().add(pieSliceCaption);
timeline.play();
}


public void stop() {
updateStatusTable();
updaterThread.interrupt();
timeline.stop();
}

public void updateWindow() {
AppContext appContext = AppContext.getInstance();
if (appContext == null) {
return;
}

Optional<BatchProcess> batchProcess = appContext.getBatchProcess();

if (batchProcess.isPresent()) {
final Optional<AsyncStatus> status = batchProcess.get().checkAsyncStatus();

if (!status.isEmpty()) {
updatePieChart(status.get());
updateTotalToProcess(status.get());
updateStatusTable();
}
BatchProcess.STATUS batchProcessStatus = batchProcess.get().getMutableStatus().get();
overallStatus.setText(batchProcessStatus.name());
if (batchProcessStatus != BatchProcess.STATUS.RUNNING) {
stop();
}
}
}

private void updateStatusTable() {
Expand All @@ -118,126 +170,99 @@ private void updateStatusTable() {
statusTable.refresh();
}

private class Updater implements Runnable {
@Override
public void run() {
statusPieChart.setData(pieChartData);
AppContext appContext = AppContext.getInstance();
overallStatus.setText("Running");
while (true) {
Optional<BatchProcess> batchProcess = appContext.getBatchProcess();

if (batchProcess.isPresent()) {
final Optional<AsyncStatus> status = batchProcess.get().checkAsyncStatus();

if (!status.isEmpty()) {
Platform.runLater(() -> {
updatePieChart(status.get());
updateTotalToProcess(status.get());
updateStatusTable();
});
}
BatchProcess.STATUS batchProcessStatus =
batchProcess.get().getMutableStatus().get();
overallStatus.setText(batchProcessStatus.name());
if (batchProcessStatus != BatchProcess.STATUS.RUNNING) {
return;
}
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
return;
}
}
}


private void updateTotalToProcess(AsyncStatus status) {
TotalCountResult r = status.getTotalCountResult();
String cntString = Long.toString(r.getTotalCount());
if (r.getStatus() == TotalCountResult.STATUS.NOT_COMPLETED) {
cntString += " (so far)";
}
totalToProcess.setText(cntString);
private void updateTotalToProcess(AsyncStatus status) {
TotalCountResult r = status.getTotalCountResult();
String cntString = Long.toString(r.getTotalCount());
if (r.getStatus() == TotalCountResult.STATUS.NOT_COMPLETED) {
cntString += " (so far)";
}
totalToProcess.setText(cntString);
}

private void updatePieChart(AsyncStatus status) {
private void updatePieChart(AsyncStatus status) {

long totalCount = status.getTotalCountResult().getTotalCount();
long processed = countProcessed(status);
long unprocessed = 0;
if (totalCount > processed) {
unprocessed = totalCount - processed;
}
if (pieChartData.size() == 0) {
addData("UNPROCESSED", unprocessed);
}
Set<PipesResult.STATUS> seen = new HashSet<>();
for (PieChart.Data d : pieChartData) {
String name = d.nameProperty().get();
if (name.equals("UNPROCESSED")) {
d.setPieValue(unprocessed);
} else {
PipesResult.STATUS s = lookup(name);
if (s != null) {
Long value = status.getStatusCounts().get(s);
if (value != null) {
d.setPieValue(value);
}
seen.add(s);
long totalCount = status.getTotalCountResult().getTotalCount();
long processed = countProcessed(status);
long unprocessed = 0;
if (totalCount > processed) {
unprocessed = totalCount - processed;
}
if (pieChartData.size() == 0) {
addData("UNPROCESSED", unprocessed);
}
Set<PipesResult.STATUS> seen = new HashSet<>();
for (PieChart.Data d : pieChartData) {
String name = d.nameProperty().get();
if (name.equals("UNPROCESSED")) {
d.setPieValue(unprocessed);
} else {
PipesResult.STATUS s = lookup(name);
if (s != null) {
Long value = status.getStatusCounts().get(s);
if (value != null) {
d.setPieValue(value);
}
seen.add(s);
}
}
for (Map.Entry<PipesResult.STATUS, Long> e : status.getStatusCounts().entrySet()) {
if (!seen.contains(e.getKey())) {
addData(e.getKey().name(), e.getValue());
}
}
for (Map.Entry<PipesResult.STATUS, Long> e : status.getStatusCounts().entrySet()) {
if (!seen.contains(e.getKey())) {
addData(e.getKey().name(), e.getValue());
}
totalProcessed.setText(Long.toString(processed));
}
totalProcessed.setText(Long.toString(processed));
}

private void addData(String name, double value) {
PieChart.Data data = new PieChart.Data(name, value);
pieChartData.add(data);
if (name.equals("UNPROCESSED")) {
data.getNode().setStyle("-fx-pie-color: #" + UNPROCESSED_COLOR + ";");
} else {
PipesResult.STATUS status = lookup(name);
String color = COLORS.getOrDefault(status, "");
if (color.length() > 0) {
data.getNode().setStyle("-fx-pie-color: #" + color + ";");
private void addData(String name, double value) {
PieChart.Data data = new PieChart.Data(name, value);
pieChartData.add(data);
if (name.equals("UNPROCESSED")) {
data.getNode().setStyle("-fx-pie-color: #" + UNPROCESSED_COLOR + "; -fx-pie-label-visible: true;");
} else {
PipesResult.STATUS status = lookup(name);
String color = COLORS.getOrDefault(status, "");
if (color.length() > 0) {
data.getNode().setStyle("-fx-pie-color: #" + color + "; -fx-pie-label-visible: true;");
}
}

data.getNode().addEventHandler(MouseEvent.MOUSE_PRESSED, new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent e) {
double sum = 0.0;
for (PieChart.Data d : pieChartData) {
sum += d.pieValueProperty().getValue();
}
if (sum > 0) {
pieSliceCaption.setTranslateX(e.getSceneX());
pieSliceCaption.setTranslateY(e.getSceneY());
pieSliceCaption.setText(String.format(Locale.US,
data.getName() + ": " +
"%.0f%%", 100 * data.getPieValue() / sum));
}
}
});

data.getNode()
.addEventHandler(MouseEvent.MOUSE_PRESSED, new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent e) {
pieSliceCaption.setTranslateX(e.getSceneX());
pieSliceCaption.setTranslateY(e.getSceneY());
pieSliceCaption.setText(data.getPieValue() + "%");
}
});

StatusCount statusCount = new StatusCount(name, value);
statusCount.countProperty().bind(data.pieValueProperty());
statusCounts.add(statusCount);
}
StatusCount statusCount = new StatusCount(name, value);
statusCount.countProperty().bind(data.pieValueProperty());
statusCounts.add(statusCount);
}

private PipesResult.STATUS lookup(String name) {
private PipesResult.STATUS lookup(String name) {

if (!PIPES_STATUS_LOOKUP.containsKey(name)) {
return null;
}
return PIPES_STATUS_LOOKUP.get(name);
if (!PIPES_STATUS_LOOKUP.containsKey(name)) {
return null;
}
return PIPES_STATUS_LOOKUP.get(name);
}

private long countProcessed(AsyncStatus status) {
return status.getStatusCounts().values().stream().reduce(0L, Long::sum);
}
private long countProcessed(AsyncStatus status) {
return status.getStatusCounts().values().stream().reduce(0L, Long::sum);
}


private class MyDoubleStringConverter extends StringConverter<Double> {

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
<?import javafx.scene.control.cell.TextFieldTableCell?>
<?import javafx.scene.layout.Pane?>

<Pane maxHeight="800.0" maxWidth="1200.0" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1" fx:controller="org.tallison.tika.app.fx.BatchStatusController">
<Pane fx:id="statusPane" maxHeight="800.0" maxWidth="1200.0" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1" fx:controller="org.tallison.tika.app.fx.BatchStatusController">
<children>
<TextField fx:id="totalToProcess" editable="false" layoutX="146.0" layoutY="318.0" prefHeight="26.0" prefWidth="107.0" />
<TextField fx:id="totalProcessed" editable="false" layoutX="146.0" layoutY="281.0" prefHeight="26.0" prefWidth="107.0" />
Expand Down
Loading