Skip to content
This repository was archived by the owner on Sep 18, 2023. It is now read-only.

Commit 287ce41

Browse files
committed
feat: terminate NodeJS scripts on exit/close
1 parent e330dc0 commit 287ce41

File tree

3 files changed

+119
-3
lines changed

3 files changed

+119
-3
lines changed

pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,11 @@
4949
<artifactId>de-adito-metrics-api</artifactId>
5050
<version>${netbeans.version}-1.9.0</version>
5151
</dependency>
52+
<dependency>
53+
<groupId>org.netbeans.modules</groupId>
54+
<artifactId>org-netbeans-core-output2</artifactId>
55+
<version>${netbeans.version}-1.9.0</version>
56+
</dependency>
5257

5358
<!-- ADITO Utils -->
5459
<dependency>
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package de.adito.aditoweb.nbm.nodejs.impl;
2+
3+
import org.openide.*;
4+
import org.openide.modules.OnStop;
5+
import org.openide.util.NbBundle;
6+
7+
import javax.swing.*;
8+
import java.util.Set;
9+
import java.util.concurrent.*;
10+
11+
/**
12+
* Hook that is executed when the designer closes.
13+
* If NodeJS scripts are still running the user will be warned
14+
* and has the option to terminate the scripts
15+
*
16+
* @author p.neub, 28.09.2022
17+
*/
18+
@OnStop
19+
public class NodeJSScriptExitHook implements Callable<Boolean>
20+
{
21+
private static final Set<CompletableFuture<Integer>> running = ConcurrentHashMap.newKeySet();
22+
23+
@NbBundle.Messages({
24+
"LBL_ScriptExitConfirmTitle={0} NodeJS script(s) is/are still running...",
25+
"LBL_ScriptExitConfirmMessage=Terminate {0} running NodeJS script(s)?",
26+
"LBL_ScriptExitConfirmTerminateBtn=Terminate all",
27+
"LBL_ScriptExitConfirmDetachBtn=Detach all",
28+
"LBL_ScriptExitConfirmCancelBtn=Cancel exit",
29+
})
30+
@Override
31+
public Boolean call()
32+
{
33+
int count = running.size();
34+
if (count == 0)
35+
return Boolean.TRUE;
36+
NotifyDescriptor descriptor = new NotifyDescriptor.Confirmation(
37+
Bundle.LBL_ScriptExitConfirmMessage(count),
38+
Bundle.LBL_ScriptExitConfirmTitle(count));
39+
descriptor.setOptions(new String[]{
40+
Bundle.LBL_ScriptExitConfirmTerminateBtn(),
41+
Bundle.LBL_ScriptExitConfirmDetachBtn(),
42+
Bundle.LBL_ScriptExitConfirmCancelBtn(),
43+
});
44+
Object selected = DialogDisplayer.getDefault().notify(descriptor);
45+
if (NotifyDescriptor.CLOSED_OPTION.equals(selected) || Bundle.LBL_ScriptExitConfirmCancelBtn().equals(selected))
46+
return Boolean.FALSE;
47+
if (Bundle.LBL_ScriptExitConfirmTerminateBtn().equals(selected))
48+
running.forEach(f -> f.cancel(false));
49+
return Boolean.TRUE;
50+
}
51+
52+
/**
53+
* Adds a running NodeJS process,
54+
* so that it can be terminated cleanly when the Designer is closed
55+
*
56+
* @param pProcessFuture process future
57+
*/
58+
public static void add(CompletableFuture<Integer> pProcessFuture)
59+
{
60+
running.add(pProcessFuture);
61+
}
62+
63+
/**
64+
* Removes the NodeJS process so that the user will no longer be warned about the process
65+
* this should be called when the process terminates, or the process is detached from the designer
66+
*
67+
* @param pProcessFuture process future
68+
*/
69+
public static void remove(CompletableFuture<Integer> pProcessFuture)
70+
{
71+
running.remove(pProcessFuture);
72+
}
73+
74+
}

src/main/java/de/adito/aditoweb/nbm/nodejs/impl/runconfig/NodeJSScriptRunConfig.java

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package de.adito.aditoweb.nbm.nodejs.impl.runconfig;
22

33
import de.adito.aditoweb.nbm.nbide.nbaditointerface.javascript.node.*;
4+
import de.adito.aditoweb.nbm.nodejs.impl.NodeJSScriptExitHook;
45
import de.adito.aditoweb.nbm.nodejs.impl.actions.io.*;
56
import de.adito.nbm.runconfig.api.*;
67
import de.adito.nbm.runconfig.spi.IActiveConfigComponentProvider;
@@ -12,14 +13,18 @@
1213
import org.jetbrains.annotations.NotNull;
1314
import org.netbeans.api.progress.ProgressHandle;
1415
import org.netbeans.api.project.*;
16+
import org.netbeans.core.output2.adito.InputOutputExt;
17+
import org.openide.*;
18+
import org.openide.util.*;
1519
import org.openide.windows.*;
1620

1721
import javax.swing.*;
22+
import java.beans.PropertyChangeListener;
1823
import java.io.*;
1924
import java.nio.charset.StandardCharsets;
2025
import java.nio.file.Paths;
2126
import java.util.*;
22-
import java.util.concurrent.CompletableFuture;
27+
import java.util.concurrent.*;
2328

2429
/**
2530
* RunConfig to execute a single nodejs script
@@ -28,7 +33,6 @@
2833
*/
2934
class NodeJSScriptRunConfig implements IRunConfig
3035
{
31-
3236
private final Project project;
3337
private final INodeJSEnvironment environment;
3438
private final String scriptName;
@@ -86,6 +90,12 @@ public void executeAsnyc(@NotNull ProgressHandle pProgressHandle)
8690
}
8791
}
8892

93+
@NbBundle.Messages({
94+
"LBL_ScriptCloseTerminateTitle=NodeJS script is still running...",
95+
"LBL_ScriptCloseTerminateMessage=Do you want to terminate the NodeJS script?",
96+
"LBL_ScriptCloseTerminateTerminateBtn=Terminate",
97+
"LBL_ScriptCloseTerminateDetachBtn=Detach",
98+
})
8999
private void run(@NotNull InputOutput pIo, @NotNull INodeJSExecutor pExecutor, @NotNull Subject<Optional<CompletableFuture<Integer>>> pSubject)
90100
{
91101
try
@@ -107,7 +117,34 @@ private void run(@NotNull InputOutput pIo, @NotNull INodeJSExecutor pExecutor, @
107117
String npmScript = Paths.get(environment.getPath().getParent(), "node_modules", "npm", "bin", "npm-cli.js").toString();
108118
CompletableFuture<Integer> future = pExecutor.executeAsync(environment, INodeJSExecBase.node(), out, err, null, npmScript, "run", scriptName);
109119
pSubject.onNext(Optional.of(future));
110-
future.whenComplete((pExit, pEx) -> pSubject.onNext(Optional.of(future)));
120+
121+
PropertyChangeListener ioListener = evt -> {
122+
// also remove, if the script is not stopped
123+
NodeJSScriptExitHook.remove(future);
124+
125+
// no cancel option, because the closing of the output window can not be aborted here
126+
NotifyDescriptor descriptor = new NotifyDescriptor.Confirmation(
127+
Bundle.LBL_ScriptCloseTerminateMessage(),
128+
Bundle.LBL_ScriptCloseTerminateTitle());
129+
descriptor.setOptions(new String[]{
130+
Bundle.LBL_ScriptCloseTerminateTerminateBtn(),
131+
Bundle.LBL_ScriptCloseTerminateDetachBtn(),
132+
});
133+
Object selected = DialogDisplayer.getDefault().notify(descriptor);
134+
if (Bundle.LBL_ScriptCloseTerminateTerminateBtn().equals(selected))
135+
future.cancel(false);
136+
};
137+
if (pIo instanceof InputOutputExt)
138+
((InputOutputExt) pIo).addPropertyChangeListener(ioListener);
139+
140+
future.whenComplete((pExit, pEx) -> {
141+
if (pIo instanceof InputOutputExt)
142+
((InputOutputExt) pIo).removePropertyChangeListener(ioListener);
143+
144+
NodeJSScriptExitHook.remove(future);
145+
pSubject.onNext(Optional.of(future));
146+
});
147+
NodeJSScriptExitHook.add(future);
111148
}
112149
catch (IOException pEx)
113150
{

0 commit comments

Comments
 (0)