From 6f864849e7277c209a028c131f6a5453d1cff741 Mon Sep 17 00:00:00 2001 From: Mark Raynsford Date: Sun, 29 Sep 2024 10:56:31 +0000 Subject: [PATCH] Improve error reporting and state Affects: https://github.com/io7m-com/laurel/issues/72 Affects: https://github.com/io7m-com/laurel/issues/71 --- .../filemodel/LFileModelStatusError.java | 31 ++++++ .../filemodel/LFileModelStatusIdle.java | 28 +++++ .../filemodel/LFileModelStatusLoading.java | 28 +++++ .../LFileModelStatusRunningCommand.java | 28 +++++ .../filemodel/LFileModelStatusType.java | 31 ++++++ .../io7m/laurel/filemodel/LFileModelType.java | 6 ++ .../filemodel/internal/LCommandLoad.java | 3 + .../filemodel/internal/LCommandType.java | 9 ++ .../laurel/filemodel/internal/LFileModel.java | 26 +++++ com.io7m.laurel.gui/pom.xml | 8 ++ .../io7m/laurel/gui/internal/LFileView.java | 101 ++++++++++++++---- .../src/main/java/module-info.java | 2 + pom.xml | 8 +- 13 files changed, 285 insertions(+), 24 deletions(-) create mode 100644 com.io7m.laurel.filemodel/src/main/java/com/io7m/laurel/filemodel/LFileModelStatusError.java create mode 100644 com.io7m.laurel.filemodel/src/main/java/com/io7m/laurel/filemodel/LFileModelStatusIdle.java create mode 100644 com.io7m.laurel.filemodel/src/main/java/com/io7m/laurel/filemodel/LFileModelStatusLoading.java create mode 100644 com.io7m.laurel.filemodel/src/main/java/com/io7m/laurel/filemodel/LFileModelStatusRunningCommand.java create mode 100644 com.io7m.laurel.filemodel/src/main/java/com/io7m/laurel/filemodel/LFileModelStatusType.java diff --git a/com.io7m.laurel.filemodel/src/main/java/com/io7m/laurel/filemodel/LFileModelStatusError.java b/com.io7m.laurel.filemodel/src/main/java/com/io7m/laurel/filemodel/LFileModelStatusError.java new file mode 100644 index 0000000..f52497e --- /dev/null +++ b/com.io7m.laurel.filemodel/src/main/java/com/io7m/laurel/filemodel/LFileModelStatusError.java @@ -0,0 +1,31 @@ +/* + * Copyright © 2024 Mark Raynsford https://www.io7m.com + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR + * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + + +package com.io7m.laurel.filemodel; + +/** + * The file model encountered an error. + * + * @param error The error + */ + +public record LFileModelStatusError( + LFileModelEventError error) + implements LFileModelStatusType +{ + +} diff --git a/com.io7m.laurel.filemodel/src/main/java/com/io7m/laurel/filemodel/LFileModelStatusIdle.java b/com.io7m.laurel.filemodel/src/main/java/com/io7m/laurel/filemodel/LFileModelStatusIdle.java new file mode 100644 index 0000000..07c0f4f --- /dev/null +++ b/com.io7m.laurel.filemodel/src/main/java/com/io7m/laurel/filemodel/LFileModelStatusIdle.java @@ -0,0 +1,28 @@ +/* + * Copyright © 2024 Mark Raynsford https://www.io7m.com + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR + * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + + +package com.io7m.laurel.filemodel; + +/** + * The file model is idle. + */ + +public record LFileModelStatusIdle() + implements LFileModelStatusType +{ + +} diff --git a/com.io7m.laurel.filemodel/src/main/java/com/io7m/laurel/filemodel/LFileModelStatusLoading.java b/com.io7m.laurel.filemodel/src/main/java/com/io7m/laurel/filemodel/LFileModelStatusLoading.java new file mode 100644 index 0000000..2ad4b57 --- /dev/null +++ b/com.io7m.laurel.filemodel/src/main/java/com/io7m/laurel/filemodel/LFileModelStatusLoading.java @@ -0,0 +1,28 @@ +/* + * Copyright © 2024 Mark Raynsford https://www.io7m.com + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR + * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + + +package com.io7m.laurel.filemodel; + +/** + * The file model is loading. + */ + +public record LFileModelStatusLoading() + implements LFileModelStatusType +{ + +} diff --git a/com.io7m.laurel.filemodel/src/main/java/com/io7m/laurel/filemodel/LFileModelStatusRunningCommand.java b/com.io7m.laurel.filemodel/src/main/java/com/io7m/laurel/filemodel/LFileModelStatusRunningCommand.java new file mode 100644 index 0000000..8f5ec69 --- /dev/null +++ b/com.io7m.laurel.filemodel/src/main/java/com/io7m/laurel/filemodel/LFileModelStatusRunningCommand.java @@ -0,0 +1,28 @@ +/* + * Copyright © 2024 Mark Raynsford https://www.io7m.com + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR + * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + + +package com.io7m.laurel.filemodel; + +/** + * The file model is running a command. + */ + +public record LFileModelStatusRunningCommand() + implements LFileModelStatusType +{ + +} diff --git a/com.io7m.laurel.filemodel/src/main/java/com/io7m/laurel/filemodel/LFileModelStatusType.java b/com.io7m.laurel.filemodel/src/main/java/com/io7m/laurel/filemodel/LFileModelStatusType.java new file mode 100644 index 0000000..5ae166b --- /dev/null +++ b/com.io7m.laurel.filemodel/src/main/java/com/io7m/laurel/filemodel/LFileModelStatusType.java @@ -0,0 +1,31 @@ +/* + * Copyright © 2024 Mark Raynsford https://www.io7m.com + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR + * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + + +package com.io7m.laurel.filemodel; + +/** + * The status of a file model. + */ + +public sealed interface LFileModelStatusType + permits LFileModelStatusError, + LFileModelStatusIdle, + LFileModelStatusLoading, + LFileModelStatusRunningCommand +{ + +} diff --git a/com.io7m.laurel.filemodel/src/main/java/com/io7m/laurel/filemodel/LFileModelType.java b/com.io7m.laurel.filemodel/src/main/java/com/io7m/laurel/filemodel/LFileModelType.java index a564544..ac69224 100644 --- a/com.io7m.laurel.filemodel/src/main/java/com/io7m/laurel/filemodel/LFileModelType.java +++ b/com.io7m.laurel.filemodel/src/main/java/com/io7m/laurel/filemodel/LFileModelType.java @@ -54,6 +54,12 @@ public interface LFileModelType Flow.Publisher events(); + /** + * @return An observable status value for the file model + */ + + AttributeReadableType status(); + /** * Add a global caption. * diff --git a/com.io7m.laurel.filemodel/src/main/java/com/io7m/laurel/filemodel/internal/LCommandLoad.java b/com.io7m.laurel.filemodel/src/main/java/com/io7m/laurel/filemodel/internal/LCommandLoad.java index f5fe697..92cd2f7 100644 --- a/com.io7m.laurel.filemodel/src/main/java/com/io7m/laurel/filemodel/internal/LCommandLoad.java +++ b/com.io7m.laurel.filemodel/src/main/java/com/io7m/laurel/filemodel/internal/LCommandLoad.java @@ -18,6 +18,7 @@ package com.io7m.laurel.filemodel.internal; import com.io7m.darco.api.DDatabaseUnit; +import com.io7m.laurel.filemodel.LFileModelStatusLoading; import org.jooq.DSLContext; import java.util.Properties; @@ -64,6 +65,8 @@ protected LCommandUndoable onExecute( final LDatabaseTransactionType transaction, final DDatabaseUnit request) { + model.setStatus(new LFileModelStatusLoading()); + final var context = transaction.get(DSLContext.class); model.eventWithProgress(0.0, "Loading images…"); diff --git a/com.io7m.laurel.filemodel/src/main/java/com/io7m/laurel/filemodel/internal/LCommandType.java b/com.io7m.laurel.filemodel/src/main/java/com/io7m/laurel/filemodel/internal/LCommandType.java index cbfa3ba..06006ce 100644 --- a/com.io7m.laurel.filemodel/src/main/java/com/io7m/laurel/filemodel/internal/LCommandType.java +++ b/com.io7m.laurel.filemodel/src/main/java/com/io7m/laurel/filemodel/internal/LCommandType.java @@ -34,6 +34,15 @@ public interface LCommandType

{ + /** + * @return {@code true} if this command indicates the file model is loading + */ + + default boolean loading() + { + return false; + } + /** * @return {@code true} if the database should be compacted after this command */ diff --git a/com.io7m.laurel.filemodel/src/main/java/com/io7m/laurel/filemodel/internal/LFileModel.java b/com.io7m.laurel.filemodel/src/main/java/com/io7m/laurel/filemodel/internal/LFileModel.java index 5b5c7dd..70d2269 100644 --- a/com.io7m.laurel.filemodel/src/main/java/com/io7m/laurel/filemodel/internal/LFileModel.java +++ b/com.io7m.laurel.filemodel/src/main/java/com/io7m/laurel/filemodel/internal/LFileModel.java @@ -31,6 +31,10 @@ import com.io7m.laurel.filemodel.LExportRequest; import com.io7m.laurel.filemodel.LFileModelEvent; import com.io7m.laurel.filemodel.LFileModelEventType; +import com.io7m.laurel.filemodel.LFileModelStatusIdle; +import com.io7m.laurel.filemodel.LFileModelStatusLoading; +import com.io7m.laurel.filemodel.LFileModelStatusRunningCommand; +import com.io7m.laurel.filemodel.LFileModelStatusType; import com.io7m.laurel.filemodel.LFileModelType; import com.io7m.laurel.filemodel.LImageCaptionsAssignment; import com.io7m.laurel.filemodel.LImageComparison; @@ -131,6 +135,7 @@ public final class LFileModel implements LFileModelType private final SubmissionPublisher events; private final AttributeType> validationProblems; private final AttributeType> exportEvents; + private final AttributeType status; private LFileModel( final LDatabaseType inDatabase) @@ -189,6 +194,8 @@ private LFileModel( ATTRIBUTES.withValue(List.of()); this.validationProblems = ATTRIBUTES.withValue(List.of()); + this.status = + ATTRIBUTES.withValue(new LFileModelStatusLoading()); this.commandLock = new ReentrantLock(); this.attributes = @@ -523,6 +530,12 @@ public SubmissionPublisher events() return this.events; } + @Override + public AttributeReadableType status() + { + return this.status; + } + @Override public CompletableFuture globalCaptionAdd( final LCaptionName text) @@ -799,7 +812,14 @@ public CompletableFuture metadataRemove( Thread.ofVirtual() .start(() -> { try { + if (command.loading()) { + this.status.set(new LFileModelStatusLoading()); + } else { + this.status.set(new LFileModelStatusRunningCommand()); + } + this.executeCommandLocked(command, parameters); + this.status.set(new LFileModelStatusIdle()); future.complete(null); } catch (final Throwable e) { future.completeExceptionally(e); @@ -1507,4 +1527,10 @@ void setExportEvents( { this.exportEvents.set(newEvents); } + + void setStatus( + final LFileModelStatusType newStatus) + { + this.status.set(newStatus); + } } diff --git a/com.io7m.laurel.gui/pom.xml b/com.io7m.laurel.gui/pom.xml index 17cd584..ea55710 100644 --- a/com.io7m.laurel.gui/pom.xml +++ b/com.io7m.laurel.gui/pom.xml @@ -54,6 +54,14 @@ com.io7m.jwheatsheaf com.io7m.jwheatsheaf.ui + + com.io7m.miscue + com.io7m.miscue.fx.seltzer + + + com.io7m.seltzer + com.io7m.seltzer.api + org.slf4j diff --git a/com.io7m.laurel.gui/src/main/java/com/io7m/laurel/gui/internal/LFileView.java b/com.io7m.laurel.gui/src/main/java/com/io7m/laurel/gui/internal/LFileView.java index 961b3d6..290a9ad 100644 --- a/com.io7m.laurel.gui/src/main/java/com/io7m/laurel/gui/internal/LFileView.java +++ b/com.io7m.laurel.gui/src/main/java/com/io7m/laurel/gui/internal/LFileView.java @@ -22,8 +22,14 @@ import com.io7m.jwheatsheaf.api.JWFileChooserAction; import com.io7m.jwheatsheaf.api.JWFileChooserConfiguration; import com.io7m.laurel.filemodel.LFileModelEventType; +import com.io7m.laurel.filemodel.LFileModelStatusError; +import com.io7m.laurel.filemodel.LFileModelStatusIdle; +import com.io7m.laurel.filemodel.LFileModelStatusLoading; +import com.io7m.laurel.filemodel.LFileModelStatusRunningCommand; +import com.io7m.laurel.filemodel.LFileModelStatusType; import com.io7m.laurel.filemodel.LFileModelType; import com.io7m.laurel.model.LException; +import com.io7m.miscue.fx.seltzer.MSErrorDialogs; import com.io7m.repetoir.core.RPServiceDirectoryType; import javafx.application.Platform; import javafx.fxml.FXML; @@ -34,10 +40,12 @@ import javafx.scene.control.MenuItem; import javafx.scene.control.ProgressBar; import javafx.scene.layout.Pane; +import javafx.stage.Modality; import javafx.stage.Stage; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.IOException; import java.util.Map; import java.util.Objects; import java.util.Optional; @@ -115,22 +123,23 @@ private LFileView( * * @return A view and stage * - * @throws Exception On errors + * @throws LException On errors */ public static LViewAndStage openForStage( final RPServiceDirectoryType services, final LFileModelScope fileScope, final Stage stage) - throws Exception + throws LException { final var strings = services.requireService(LStrings.class); + final var resourceName = + "/com/io7m/laurel/gui/internal/main.fxml"; final var xml = - LFileView.class.getResource( - "/com/io7m/laurel/gui/internal/main.fxml" - ); + LFileView.class.getResource(resourceName); + final var resources = strings.resources(); final var loader = @@ -180,7 +189,18 @@ public static LViewAndStage openForStage( return controllers.call((Class) param); }); - final var pane = loader.load(); + final Pane pane; + try { + pane = loader.load(); + } catch (final IOException e) { + throw new LException( + Objects.requireNonNullElse(e.getMessage(), e.getClass().getName()), + e, + "error-io", + Map.of("Resource", resourceName) + ); + } + LCSS.setCSS(pane); stage.setScene(new Scene(pane)); stage.setTitle(strings.format(TITLE)); @@ -219,6 +239,12 @@ protected void onFileBecameAvailable( final CloseableCollectionType subscriptions, final LFileModelType fileModel) { + Platform.runLater(() -> { + this.mainContent.setVisible(true); + this.menuItemClose.setDisable(false); + this.menuItemExport.setDisable(false); + }); + final var eventSubscriber = subscriptions.add( new LPerpetualSubscriber(event -> { @@ -244,12 +270,31 @@ protected void onFileBecameAvailable( }) ); - Platform.runLater(() -> { - this.mainContent.setDisable(false); - this.mainContent.setVisible(true); - this.menuItemClose.setDisable(false); - this.menuItemExport.setDisable(false); - }); + subscriptions.add( + fileModel.status() + .subscribe((oldValue, newValue) -> { + Platform.runLater(() -> this.onStatusChanged(newValue)); + }) + ); + } + + private void onStatusChanged( + final LFileModelStatusType newValue) + { + switch (newValue) { + case final LFileModelStatusError error -> { + this.mainContent.setDisable(false); + } + case final LFileModelStatusIdle idle -> { + this.mainContent.setDisable(false); + } + case final LFileModelStatusLoading loading -> { + this.mainContent.setDisable(true); + } + case final LFileModelStatusRunningCommand command -> { + this.mainContent.setDisable(false); + } + } } private void onFileModelEvent( @@ -307,19 +352,24 @@ private void redoEnable( /** * The user tried to create a new image set. - * - * @throws Exception On errors */ @FXML public void onNewSelected() - throws Exception { - this.tryNew(); + try { + this.tryNew(); + } catch (final LException e) { + MSErrorDialogs.builder(e) + .setCSS(LCSS.defaultCSS()) + .setModality(Modality.APPLICATION_MODAL) + .build() + .showAndWait(); + } } private DDatabaseUnit tryNew() - throws Exception + throws LException { final var chooser = this.choosers.create( @@ -359,20 +409,25 @@ private DDatabaseUnit tryNew() } /** - * The user tried to open an image set. - * - * @throws Exception On errors + * The user tried to open a dataset. */ @FXML public void onOpenSelected() - throws Exception { - this.tryOpen(); + try { + this.tryOpen(); + } catch (final LException e) { + MSErrorDialogs.builder(e) + .setCSS(LCSS.defaultCSS()) + .setModality(Modality.APPLICATION_MODAL) + .build() + .showAndWait(); + } } private DDatabaseUnit tryOpen() - throws Exception + throws LException { final var chooser = this.choosers.create( diff --git a/com.io7m.laurel.gui/src/main/java/module-info.java b/com.io7m.laurel.gui/src/main/java/module-info.java index 49defa9..f3835ed 100644 --- a/com.io7m.laurel.gui/src/main/java/module-info.java +++ b/com.io7m.laurel.gui/src/main/java/module-info.java @@ -37,8 +37,10 @@ requires com.io7m.jwheatsheaf.oxygen; requires com.io7m.jwheatsheaf.ui; requires com.io7m.jxtrand.api; + requires com.io7m.miscue.fx.seltzer; requires com.io7m.repetoir.core; requires com.io7m.seltzer.api; + requires java.desktop; requires javafx.base; requires javafx.controls; diff --git a/pom.xml b/pom.xml index 695de5f..bede335 100644 --- a/pom.xml +++ b/pom.xml @@ -47,6 +47,7 @@ 1.1.0 1.9.0 1.0.0 + 0.0.3 1.42.1 @@ -174,7 +175,12 @@ com.io7m.miscue com.io7m.miscue.core - 0.0.3 + ${com.io7m.miscue.version} + + + com.io7m.miscue + com.io7m.miscue.fx.seltzer + ${com.io7m.miscue.version} com.io7m.jade