Skip to content

add dialog to Launcher#11800

Merged
koppor merged 9 commits into
JabRef:mainfrom
leaf-soba:close-issue-11783
Sep 29, 2024
Merged

add dialog to Launcher#11800
koppor merged 9 commits into
JabRef:mainfrom
leaf-soba:close-issue-11783

Conversation

@leaf-soba

@leaf-soba leaf-soba commented Sep 20, 2024

Copy link
Copy Markdown
Contributor
  • Closes Add a visible notice when user opens more than one JabRef instance #11783
  • when user open two instances of JabRef, second one will open dialog saying Localization.lang("Another JabRef instance is already running. Please switch to that instance.")
  • since DialogService didn't initialize at that life cycle, I tried some way to get around it but all failed, such as:
  1. NPE
dialogService.showInformationDialogAndWait(Localization.lang("Opening multiple JabRef instance without arguments"), Localization.lang("Another JabRef instance is already running. Please switch to that instance."));
  1. java.lang.IllegalStateException: This operation is permitted on the event thread only; currentThread = main
JabRefDialogService dialogService = new JabRefDialogService(new BaseWindow());
dialogService.showInformationDialogAndWait(Localization.lang("Opening multiple JabRef instance without arguments"), Localization.lang("Another JabRef instance is already running. Please switch to that instance."));
  1. Unexpected exception: java.lang.IllegalStateException: Toolkit not initialized
UiTaskExecutor.runInJavaFXThread(() -> {
    DialogService dialogService = Injector.instantiateModelOrService(DialogService.class);
    dialogService.showErrorDialogAndWait(Localization.lang("Another JabRef instance is already running. Please switch to that instance."));
}));
  1. Nothing happened; it seems the JavaFXThread wasn't created before the instance was terminated
                    FallbackExceptionHandler.installExceptionHandler((exception, thread) -> {
                        UiTaskExecutor.runInJavaFXThread(() -> {
                            DialogService dialogService = Injector.instantiateModelOrService(DialogService.class);
                            dialogService.showErrorDialogAndWait("Another JabRef instance is already running. Please switch to that instance.");
                        });
                    });
  • so I add JOptionPane to open a dialog, but I think it may not the best practice in this situation.
2024-09-20 20 28 33

edit: I changed to JavaFX now:
2024-09-21 10 57 36


edit:

  1. removed swing based on comment
  2. open a dialog in exist instance
    2024-09-21 16 25 42

edit: fix base on devcall

  1. fix a little bug in shutdownThreadPools() when taskExecutor is null, it can't be shutdown.
  2. open a dialog after initialize()
    2024-09-26 14 25 56

Mandatory checks

  • Change in CHANGELOG.md described in a way that is understandable for the average user (if applicable)
  • Tests created for changes (if applicable)
  • Manually tested changed features in running JabRef (always required)
  • Screenshots added in PR description (for UI changes)
  • Checked developer's documentation: Is the information available and up to date? If not, I outlined it in this pull request.
  • Checked documentation: Is the information available and up to date? If not, I created an issue at https://github.com/JabRef/user-documentation/issues or, even better, I submitted a pull request to the documentation repository.

when user open two instances of JabRef, second one will open dialog saying Localization.lang("Another JabRef instance is already running. Please switch to that instance.")
Comment thread src/main/java/org/jabref/Launcher.java Outdated
Comment on lines +14 to +15
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;

@subhramit subhramit Sep 20, 2024

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we use swing in JabRef (we migrated long back). Can you find JavaFX equivalents and use them?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but the javafx is not yet initialized only in JabrefGui.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A workaround would be to add a consumer lambda as a parameter to the method handleMultipleAppInstances and then pass that down to JabRefGUI where you can use it to show a dialog
I did a similar thing for the FallbackExceptionHandler

@leaf-soba leaf-soba Sep 21, 2024

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A workaround would be to add a consumer lambda as a parameter to the method handleMultipleAppInstances and then pass that down to JabRefGUI where you can use it to show a dialog I did a similar thing for the FallbackExceptionHandler

I have tried your solution yesterday, sorry I forget to include it into origin post:
4. Nothing happened; it seems the JavaFXThread wasn't created before the instance was terminated

                    FallbackExceptionHandler.installExceptionHandler((exception, thread) -> {
                        UiTaskExecutor.runInJavaFXThread(() -> {
                            DialogService dialogService = Injector.instantiateModelOrService(DialogService.class);
                            dialogService.showErrorDialogAndWait("Another JabRef instance is already running. Please switch to that instance.");
                        });
                    });

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Creating the javafx platform is costly and takes time. So displaying a message would require to wait for the platform to be initialized.

change it in JavaFX implement
@calixtus

Copy link
Copy Markdown
Member

Hi, thanks for your efforts.
There are several constraints to keep in mind:

  • Use of Swing is not allowed, except for undo-redo. And we are even thinking of moving away to are more modern framework working with the command design pattern.
  • The GUI is constructed in JabRefGui, the Launcher is just there to do the most basic app setup and to call the argument processor, then the GUI.
  • there is no proper di framework, only afterburner.fx . Afterburner does not understand a Singleton and cannot just inject without the programmer initializing/running the GUI first and creating the instance for the injection map.

My solution strategy for this would be either to put a message in the ui message list handle it in the GUI and then tear the GUI down again -or - to display the message in the already running instance.

1. Refactor the message when user starts JabRef a second time. now it is"Another JabRef instance is already running. Please switch to that instance." not "Passing arguments passed on to running JabRef..."
2. send a message from second instance to first instance to open a dialog in exist instance
@koppor

koppor commented Sep 21, 2024

Copy link
Copy Markdown
Member

Adding label devcall since this is more controversial...

@calixtus

calixtus commented Sep 23, 2024

Copy link
Copy Markdown
Member

DevCall decision:
Hi leaf-soba, we discussed your approach, that looked certainly interesting, but decided against it, because it would be using magic strings to be send to the remote client. We thought about a different approach:
You could check in the Launcher for a second instance and if it exists, add an uiCommand to the list sent to JabRefGUI::setup.
This can be intercepted in JabRefGUI::start before running JabRefGUI::initialize a dialog be shown and then javafx torne down.

We prepared a sketch diff:

diff --git a/src/main/java/org/jabref/Launcher.java b/src/main/java/org/jabref/Launcher.java
index 1d3cb9cb3e..e5a04684b7 100644
--- a/src/main/java/org/jabref/Launcher.java
+++ b/src/main/java/org/jabref/Launcher.java
@@ -109,6 +109,7 @@ public class Launcher {
                 }

                 List<UiCommand> uiCommands = new ArrayList<>(argumentProcessor.getUiCommands());
+                uiCommands.add(new UiCommand.InstanceAlreadyRunning());
                 JabRefGUI.setup(uiCommands, preferences, fileUpdateMonitor);
                 JabRefGUI.launch(JabRefGUI.class, args);
             } catch (ParseException e) {
diff --git a/src/main/java/org/jabref/gui/JabRefGUI.java b/src/main/java/org/jabref/gui/JabRefGUI.java
index d6a80bf394..1ab30890a1 100644
--- a/src/main/java/org/jabref/gui/JabRefGUI.java
+++ b/src/main/java/org/jabref/gui/JabRefGUI.java
@@ -94,6 +94,12 @@ public class JabRefGUI extends Application {
             });
         });

+        if (uiCommands.stream().anyMatch(UiCommand.InstanceAlreadyRunning.class::isInstance)) {
+            // displayDialog
+            Platform.exit();
+            return;
+        }
+
         initialize();

         JabRefGUI.mainFrame = new JabRefFrame(
diff --git a/src/main/java/org/jabref/logic/UiCommand.java b/src/main/java/org/jabref/logic/UiCommand.java
index 1fc8a699ce..019d09983f 100644
--- a/src/main/java/org/jabref/logic/UiCommand.java
+++ b/src/main/java/org/jabref/logic/UiCommand.java
@@ -12,4 +12,6 @@ public sealed interface UiCommand {
     record OpenDatabases(List<ParserResult> parserResults) implements UiCommand { }

     record AutoSetFileLinks(List<ParserResult> parserResults) implements UiCommand { }
+
+    record InstanceAlreadyRunning() implements UiCommand { }
 }

Thanks for your patience!

@leaf-soba

Copy link
Copy Markdown
Contributor Author

DevCall decision: Hi leaf-soba, we discussed your approach, that looked certainly interesting, but decided against it, because it would be using magic strings to be send to the remote client. We thought about a different approach: You could check in the Launcher for a second instance and if it exists, add an uiCommand to the list sent to JabRefGUI::setup. This can be intercepted in JabRefGUI::start before running JabRefGUI::initialize a dialog be shown and then javafx torne down.

We prepared a sketch diff:

diff --git a/src/main/java/org/jabref/Launcher.java b/src/main/java/org/jabref/Launcher.java
index 1d3cb9cb3e..e5a04684b7 100644
--- a/src/main/java/org/jabref/Launcher.java
+++ b/src/main/java/org/jabref/Launcher.java
@@ -109,6 +109,7 @@ public class Launcher {
                 }

                 List<UiCommand> uiCommands = new ArrayList<>(argumentProcessor.getUiCommands());
+                uiCommands.add(new UiCommand.InstanceAlreadyRunning());
                 JabRefGUI.setup(uiCommands, preferences, fileUpdateMonitor);
                 JabRefGUI.launch(JabRefGUI.class, args);
             } catch (ParseException e) {
diff --git a/src/main/java/org/jabref/gui/JabRefGUI.java b/src/main/java/org/jabref/gui/JabRefGUI.java
index d6a80bf394..1ab30890a1 100644
--- a/src/main/java/org/jabref/gui/JabRefGUI.java
+++ b/src/main/java/org/jabref/gui/JabRefGUI.java
@@ -94,6 +94,12 @@ public class JabRefGUI extends Application {
             });
         });

+        if (uiCommands.stream().anyMatch(UiCommand.InstanceAlreadyRunning.class::isInstance)) {
+            // displayDialog
+            Platform.exit();
+            return;
+        }
+
         initialize();

         JabRefGUI.mainFrame = new JabRefFrame(
diff --git a/src/main/java/org/jabref/logic/UiCommand.java b/src/main/java/org/jabref/logic/UiCommand.java
index 1fc8a699ce..019d09983f 100644
--- a/src/main/java/org/jabref/logic/UiCommand.java
+++ b/src/main/java/org/jabref/logic/UiCommand.java
@@ -12,4 +12,6 @@ public sealed interface UiCommand {
     record OpenDatabases(List<ParserResult> parserResults) implements UiCommand { }

     record AutoSetFileLinks(List<ParserResult> parserResults) implements UiCommand { }
+
+    record InstanceAlreadyRunning() implements UiCommand { }
 }

Thanks for your patience!

I tried your solution, got an error said ERROR: Unable to close AI service: java.lang.NullPointerException: Cannot invoke "org.jabref.logic.ai.AiService.close()" because "org.jabref.gui.JabRefGUI.aiService" is null
at

Platform.exit()

It seems this solution add more unnecessary difficulty to this simple issue.If we don't like "magic strings", I think change the string to a formal command string would be better?

@calixtus

Copy link
Copy Markdown
Member

Can you push your changes please?
And @InAnYan can you take a look? Seems like the aiService is not shutting down properly at this stage...

@InAnYan

InAnYan commented Sep 24, 2024

Copy link
Copy Markdown
Member

@leaf-soba, if you have some time, could you post the full stacktrace here? I thought this aiService.close() isn't called...

In any ways, quick win: before calling aiService.close(), check if it's not null. Something like this:

if (aiService != null) {
	aiService.close();
}

I know I should not close the first instance, I'm testing. I push this code since @calixtus ask me to push it.
@leaf-soba

Copy link
Copy Markdown
Contributor Author

Can you push your changes please? And @InAnYan can you take a look? Seems like the aiService is not shutting down properly at this stage...

done, sry I thought I should not push a test code to a branch in this project, so I just described my error which might be confusing.

- a little difference between DevCall decision, since before `initialize()` the UiTaskExecutor and dialogService is null, I arrange the dialog code after `initialize()`
@koppor

koppor commented Sep 26, 2024

Copy link
Copy Markdown
Member

I never thought it would be that much effort to show a simple JavaFX window... Sorry for that!

Do we really need to fire up the main window ( openWindow())? -- Isn't it possible to just show the dialog? "dialog" interpreted in a more broader sence: A small window showing something... -- I would be fine in having another class extends Application showing only that. But @Siedlerchr was agagainst it?!

@Siedlerchr

Copy link
Copy Markdown
Member

Extending Application here would probably cause more problems than it solves

@calixtus

Copy link
Copy Markdown
Member

Extending application (in a separate class) is just the same effort as running JabRefGui. Most computing time is used by creating the ui thread by javafx, so there would be absolutely no benefit.

@calixtus

Copy link
Copy Markdown
Member

Only adding complexity that needs to be maintained and a very weird two-javafx-application-setup in one app.

@leaf-soba

Copy link
Copy Markdown
Contributor Author

I never thought it would be that much effort to show a simple JavaFX window... Sorry for that!

Do we really need to fire up the main window ( openWindow())? -- Isn't it possible to just show the dialog? "dialog" interpreted in a more broader sence: A small window showing something... -- I would be fine in having another class extends Application showing only that. But @Siedlerchr was agagainst it?!

If I can use swing like second solution it is simple, but we don't like swing.

@calixtus

Copy link
Copy Markdown
Member

We decided against swing. Years ago.
In theory everything is simple. Yet it would overturn and ridicule decisions made and ourselves.
Also the Launcher is not part of GUI, but of headless CLI package. If we plan to move forward into multi module build, this would be a major step back to include ui stuff in the Launcher.

@koppor

koppor commented Sep 26, 2024

Copy link
Copy Markdown
Member

@leaf-soba Thank you for the efforts invested here. It turned out that the wish causes many code changes. @calixtus, @Siedlerchr and other devleopers in the devcall on Monday invested much time in discussing this. I think, at least two hours in total. Seeing the ongoing discussions and introduced architectural WTFs, it is simply not worth the current and future maintainence effort.

We will come back to the issue if some more users complain.

Internally, we will check our processes to ensure that issues are marked as "good first issues" after they were properly reviewed.


Nevertheless, can you modify this PR to NOT do any command passing, but keeping the Java code around the check of args.lgenth == 0?

                // There is already a server out there, avoid showing log "Passing arguments" while no arguments are provided.
                if (args.length == 0) {
                    LOGGER.debug("This JabRef instance is already running. Please switch to that instance.");
                } else {

@leaf-soba

Copy link
Copy Markdown
Contributor Author

@leaf-soba Thank you for the efforts invested here. It turned out that the wish causes many code changes. @calixtus, @Siedlerchr and other devleopers in the devcall on Monday invested much time in discussing this. I think, at least two hours in total. Seeing the ongoing discussions and introduced architectural WTFs, it is simply not worth the current and future maintainence effort.

We will come back to the issue if some more users complain.

Internally, we will check our processes to ensure that issues are marked as "good first issues" after they were properly reviewed.

Nevertheless, can you modify this PR to NOT do any command passing, but keeping the Java code around the check of args.lgenth == 0?

                // There is already a server out there, avoid showing log "Passing arguments" while no arguments are provided.
                if (args.length == 0) {
                    LOGGER.debug("This JabRef instance is already running. Please switch to that instance.");
                } else {

OK, actually I do love doing a lot of changes based on comments and enjoy this times in this issue, since I have learned a lot from it, thank you very much.

@leaf-soba

Copy link
Copy Markdown
Contributor Author

I think I should keep this code in org.jabref.gui.JabRefGUI#shutdownThreadPools?

if (taskExecutor != null) {
    taskExecutor.shutdown();
}

1. only change debug log without dialog
2. add a null check before closing taskExecutor
@koppor

koppor commented Sep 27, 2024

Copy link
Copy Markdown
Member

I think I should keep this code in org.jabref.gui.JabRefGUI#shutdownThreadPools?

if (taskExecutor != null) {
    taskExecutor.shutdown();
}

I am unsure, but OK for me :)

Comment thread src/main/java/org/jabref/Launcher.java Outdated
return false;
// There is already a server out there, avoid showing log "Passing arguments" while no arguments are provided.
if (args.length == 0) {
LOGGER.debug("This JabRef instance is already running. Please switch to that instance.");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Higher log level to ensure that is really output on the console.

Suggested change
LOGGER.debug("This JabRef instance is already running. Please switch to that instance.");
LOGGER.warn("This JabRef instance is already running. Please switch to that instance.");

Comment thread src/main/java/org/jabref/Launcher.java Outdated
// Output to both to the log and the screen. Therefore, we do not have an additional System.out.println.
LOGGER.info("Arguments passed on to running JabRef instance. Shutting down.");
return false;
// There is already a server out there, avoid showing log "Passing arguments" while no arguments are provided.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you move that inside the if? (meaning one line down and indent)

fix comment position
dialogService.showErrorDialogAndWait("Uncaught exception occurred in " + thread, exception);
});
});

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think, this is not consistent with the other style in this method, where „everything“ is separated with an empty line. Thus, can you revert this please? 😅

fix revert missing empty line
@leaf-soba leaf-soba requested a review from koppor September 29, 2024 01:05

@koppor koppor left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the follow-ups.

I found another thing which is strange ^^

Comment thread src/main/java/org/jabref/Launcher.java Outdated
"We do not launch a new instance in presence of an error" to "We do not launch a new instance in presence if there is another instance running"
@leaf-soba leaf-soba requested a review from koppor September 29, 2024 13:49
@koppor koppor added this pull request to the merge queue Sep 29, 2024
Merged via the queue into JabRef:main with commit e395853 Sep 29, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add a visible notice when user opens more than one JabRef instance

6 participants