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

Commit

Permalink
feat: terminate NodeJS scripts on exit/close
Browse files Browse the repository at this point in the history
  • Loading branch information
pas-n committed Sep 29, 2022
1 parent e330dc0 commit 287ce41
Show file tree
Hide file tree
Showing 3 changed files with 119 additions and 3 deletions.
5 changes: 5 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@
<artifactId>de-adito-metrics-api</artifactId>
<version>${netbeans.version}-1.9.0</version>
</dependency>
<dependency>
<groupId>org.netbeans.modules</groupId>
<artifactId>org-netbeans-core-output2</artifactId>
<version>${netbeans.version}-1.9.0</version>
</dependency>

<!-- ADITO Utils -->
<dependency>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package de.adito.aditoweb.nbm.nodejs.impl;

import org.openide.*;
import org.openide.modules.OnStop;
import org.openide.util.NbBundle;

import javax.swing.*;
import java.util.Set;
import java.util.concurrent.*;

/**
* Hook that is executed when the designer closes.
* If NodeJS scripts are still running the user will be warned
* and has the option to terminate the scripts
*
* @author p.neub, 28.09.2022
*/
@OnStop
public class NodeJSScriptExitHook implements Callable<Boolean>
{
private static final Set<CompletableFuture<Integer>> running = ConcurrentHashMap.newKeySet();

@NbBundle.Messages({
"LBL_ScriptExitConfirmTitle={0} NodeJS script(s) is/are still running...",
"LBL_ScriptExitConfirmMessage=Terminate {0} running NodeJS script(s)?",
"LBL_ScriptExitConfirmTerminateBtn=Terminate all",
"LBL_ScriptExitConfirmDetachBtn=Detach all",
"LBL_ScriptExitConfirmCancelBtn=Cancel exit",
})
@Override
public Boolean call()
{
int count = running.size();
if (count == 0)
return Boolean.TRUE;
NotifyDescriptor descriptor = new NotifyDescriptor.Confirmation(
Bundle.LBL_ScriptExitConfirmMessage(count),
Bundle.LBL_ScriptExitConfirmTitle(count));
descriptor.setOptions(new String[]{
Bundle.LBL_ScriptExitConfirmTerminateBtn(),
Bundle.LBL_ScriptExitConfirmDetachBtn(),
Bundle.LBL_ScriptExitConfirmCancelBtn(),
});
Object selected = DialogDisplayer.getDefault().notify(descriptor);
if (NotifyDescriptor.CLOSED_OPTION.equals(selected) || Bundle.LBL_ScriptExitConfirmCancelBtn().equals(selected))
return Boolean.FALSE;
if (Bundle.LBL_ScriptExitConfirmTerminateBtn().equals(selected))
running.forEach(f -> f.cancel(false));
return Boolean.TRUE;
}

/**
* Adds a running NodeJS process,
* so that it can be terminated cleanly when the Designer is closed
*
* @param pProcessFuture process future
*/
public static void add(CompletableFuture<Integer> pProcessFuture)
{
running.add(pProcessFuture);
}

/**
* Removes the NodeJS process so that the user will no longer be warned about the process
* this should be called when the process terminates, or the process is detached from the designer
*
* @param pProcessFuture process future
*/
public static void remove(CompletableFuture<Integer> pProcessFuture)
{
running.remove(pProcessFuture);
}

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package de.adito.aditoweb.nbm.nodejs.impl.runconfig;

import de.adito.aditoweb.nbm.nbide.nbaditointerface.javascript.node.*;
import de.adito.aditoweb.nbm.nodejs.impl.NodeJSScriptExitHook;
import de.adito.aditoweb.nbm.nodejs.impl.actions.io.*;
import de.adito.nbm.runconfig.api.*;
import de.adito.nbm.runconfig.spi.IActiveConfigComponentProvider;
Expand All @@ -12,14 +13,18 @@
import org.jetbrains.annotations.NotNull;
import org.netbeans.api.progress.ProgressHandle;
import org.netbeans.api.project.*;
import org.netbeans.core.output2.adito.InputOutputExt;
import org.openide.*;
import org.openide.util.*;
import org.openide.windows.*;

import javax.swing.*;
import java.beans.PropertyChangeListener;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Paths;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.*;

/**
* RunConfig to execute a single nodejs script
Expand All @@ -28,7 +33,6 @@
*/
class NodeJSScriptRunConfig implements IRunConfig
{

private final Project project;
private final INodeJSEnvironment environment;
private final String scriptName;
Expand Down Expand Up @@ -86,6 +90,12 @@ public void executeAsnyc(@NotNull ProgressHandle pProgressHandle)
}
}

@NbBundle.Messages({
"LBL_ScriptCloseTerminateTitle=NodeJS script is still running...",
"LBL_ScriptCloseTerminateMessage=Do you want to terminate the NodeJS script?",
"LBL_ScriptCloseTerminateTerminateBtn=Terminate",
"LBL_ScriptCloseTerminateDetachBtn=Detach",
})
private void run(@NotNull InputOutput pIo, @NotNull INodeJSExecutor pExecutor, @NotNull Subject<Optional<CompletableFuture<Integer>>> pSubject)
{
try
Expand All @@ -107,7 +117,34 @@ private void run(@NotNull InputOutput pIo, @NotNull INodeJSExecutor pExecutor, @
String npmScript = Paths.get(environment.getPath().getParent(), "node_modules", "npm", "bin", "npm-cli.js").toString();
CompletableFuture<Integer> future = pExecutor.executeAsync(environment, INodeJSExecBase.node(), out, err, null, npmScript, "run", scriptName);
pSubject.onNext(Optional.of(future));
future.whenComplete((pExit, pEx) -> pSubject.onNext(Optional.of(future)));

PropertyChangeListener ioListener = evt -> {
// also remove, if the script is not stopped
NodeJSScriptExitHook.remove(future);

// no cancel option, because the closing of the output window can not be aborted here
NotifyDescriptor descriptor = new NotifyDescriptor.Confirmation(
Bundle.LBL_ScriptCloseTerminateMessage(),
Bundle.LBL_ScriptCloseTerminateTitle());
descriptor.setOptions(new String[]{
Bundle.LBL_ScriptCloseTerminateTerminateBtn(),
Bundle.LBL_ScriptCloseTerminateDetachBtn(),
});
Object selected = DialogDisplayer.getDefault().notify(descriptor);
if (Bundle.LBL_ScriptCloseTerminateTerminateBtn().equals(selected))
future.cancel(false);
};
if (pIo instanceof InputOutputExt)
((InputOutputExt) pIo).addPropertyChangeListener(ioListener);

future.whenComplete((pExit, pEx) -> {
if (pIo instanceof InputOutputExt)
((InputOutputExt) pIo).removePropertyChangeListener(ioListener);

NodeJSScriptExitHook.remove(future);
pSubject.onNext(Optional.of(future));
});
NodeJSScriptExitHook.add(future);
}
catch (IOException pEx)
{
Expand Down

0 comments on commit 287ce41

Please sign in to comment.