import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
+import java.io.PrintStream;
import java.lang.System.Logger;
import java.lang.management.ManagementFactory;
import java.net.StandardSocketOptions;
import java.net.UnixDomainSocketAddress;
import java.nio.ByteBuffer;
+import java.nio.channels.AsynchronousCloseException;
import java.nio.channels.Channels;
import java.nio.channels.ClosedByInterruptException;
import java.nio.channels.ReadableByteChannel;
import java.util.Map;
import java.util.UUID;
+/** A JShell client to a local CMS node. */
public class JShellClient {
private final static Logger logger = System.getLogger(JShellClient.class.getName());
ctl.setOutputStream(System.err);
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
- System.out.println("\nShutting down...");
+// System.out.println("\nShutting down...");
toOriginalTerminal();
std.shutdown();
ctl.shutdown();
}
- public static void main(String[] args) throws IOException, InterruptedException {
- if (benchmark)
- System.err.println(ManagementFactory.getRuntimeMXBean().getUptime());
- List<String> plainArgs = new ArrayList<>();
- Map<String, List<String>> options = new HashMap<>();
- String currentOption = null;
- for (int i = 0; i < args.length; i++) {
- if (args[i].startsWith("-")) {
- currentOption = args[i];
- if (!options.containsKey(currentOption))
- options.put(currentOption, new ArrayList<>());
- i++;
- options.get(currentOption).add(args[i]);
- } else {
- plainArgs.add(args[i]);
+ public static void main(String[] args) {
+ try {
+ if (benchmark)
+ System.err.println(ManagementFactory.getRuntimeMXBean().getUptime());
+ List<String> plainArgs = new ArrayList<>();
+ Map<String, List<String>> options = new HashMap<>();
+ String currentOption = null;
+ for (int i = 0; i < args.length; i++) {
+ if (args[i].startsWith("-")) {
+ currentOption = args[i];
+ if ("-h".equals(currentOption) || "--help".equals(currentOption)) {
+ printHelp(System.out);
+ return;
+ }
+ if (!options.containsKey(currentOption))
+ options.put(currentOption, new ArrayList<>());
+ i++;
+ options.get(currentOption).add(args[i]);
+ } else {
+ plainArgs.add(args[i]);
+ }
+ }
+
+ List<String> dir = opt(options, "-d", "--sockets-dir");
+ if (dir.size() > 1)
+ throw new IllegalArgumentException("Only one run directory can be specified");
+ Path targetStateDirectory;
+ if (dir.isEmpty())
+ targetStateDirectory = Paths.get(System.getProperty("user.dir"));
+ else {
+ targetStateDirectory = Paths.get(dir.get(0));
+ if (!Files.exists(targetStateDirectory)) {
+ // we assume argument is the application id
+ targetStateDirectory = getRunDir().resolve(dir.get(0));
+ }
}
+
+ List<String> bundle = opt(options, "-b", "--bundle");
+ if (bundle.size() > 1)
+ throw new IllegalArgumentException("Only one bundle can be specified");
+ String symbolicName = bundle.isEmpty() ? "org.argeo.cms.cli" : bundle.get(0);
+
+ Path script = plainArgs.isEmpty() ? null : Paths.get(plainArgs.get(0));
+ List<String> scriptArgs = new ArrayList<>();
+ for (int i = 1; i < plainArgs.size(); i++)
+ scriptArgs.add(plainArgs.get(i));
+
+ JShellClient client = new JShellClient(targetStateDirectory, symbolicName, script, scriptArgs);
+ client.run();
+ } catch (Exception e) {
+ e.printStackTrace();
+ printHelp(System.err);
}
+ }
- Path targetStateDirectory = Paths.get(options.get("-d").get(0));
- String symbolicName = options.get("-b").get(0);
+ /** Guaranteed to return a non-null list (which may be empty). */
+ private static List<String> opt(Map<String, List<String>> options, String shortOpt, String longOpt) {
+ List<String> res = new ArrayList<>();
+ if (options.get(shortOpt) != null)
+ res.addAll(options.get(shortOpt));
+ if (options.get(longOpt) != null)
+ res.addAll(options.get(longOpt));
+ return res;
+ }
- Path script = plainArgs.isEmpty() ? null : Paths.get(plainArgs.get(0));
- List<String> scriptArgs = new ArrayList<>();
- for (int i = 1; i < plainArgs.size(); i++)
- scriptArgs.add(plainArgs.get(i));
+ public static void printHelp(PrintStream out) {
+ out.println("Start a JShell terminal or execute a JShell script in a local Argeo CMS instance");
+ out.println("Usage: jshc -d <sockets directory> -b <bundle> [JShell script] [script arguments...]");
+ out.println(" -d, --sockets-dir app directory with UNIX sockets (default to current dir)");
+ out.println(" -b, --bundle bundle to activate and use as context (default to org.argeo.cms.cli)");
+ out.println(" -h, --help this help message");
+ }
- JShellClient client = new JShellClient(targetStateDirectory, symbolicName, script, scriptArgs);
- client.run();
+ // Copied from org.argeo.cms.util.OS
+ private static Path getRunDir() {
+ Path runDir;
+ String xdgRunDir = System.getenv("XDG_RUNTIME_DIR");
+ if (xdgRunDir != null) {
+ // TODO support multiple names
+ runDir = Paths.get(xdgRunDir);
+ } else {
+ String username = System.getProperty("user.name");
+ if (username.equals("root")) {
+ runDir = Paths.get("/run");
+ } else {
+ Path homeDir = Paths.get(System.getProperty("user.home"));
+ if (!Files.isWritable(homeDir)) {
+ // typically, dameon's home (/usr/sbin) is not writable
+ runDir = Paths.get("/tmp/" + username + "/run");
+ } else {
+ runDir = homeDir.resolve(".cache/argeo");
+ }
+ }
+ }
+ return runDir;
}
/*
if (benchmark)
System.err.println(ManagementFactory.getRuntimeMXBean().getUptime());
StringBuilder sb = new StringBuilder();
-// sb.append("/set feedback silent\n");
if (!scriptArgs.isEmpty()) {
// additional arguments as $1, $2, etc.
for (String arg : scriptArgs)
if (sb.length() > 0)
writeLine(sb);
- ByteBuffer buffer = ByteBuffer.allocate(1024);
try (BufferedReader reader = Files.newBufferedReader(script)) {
String line;
lines: while ((line = reader.readLine()) != null) {
if (line.startsWith("#"))
continue lines;
- buffer.put((line + "\n").getBytes(UTF_8));
- buffer.flip();
- channel.write(buffer);
- buffer.rewind();
+ writeLine(line);
}
}
-// ByteBuffer buffer = ByteBuffer.allocate(1024);
-// try (SeekableByteChannel scriptChannel = Files.newByteChannel(script, StandardOpenOption.READ)) {
-// while (channel.isConnected()) {
-// if (scriptChannel.read(buffer) < 0)
-// break;
-// buffer.flip();
-// channel.write(buffer);
-// buffer.rewind();
-// }
-// }
-
// exit
if (channel.isConnected())
writeLine("/exit");
}
}
+ /** Not optimal, but performance is not critical here. */
private void writeLine(Object obj) throws IOException {
channel.write(ByteBuffer.wrap((obj + "\n").getBytes(UTF_8)));
}
}
} catch (ClosedByInterruptException e) {
// silent
+ } catch (AsynchronousCloseException e) {
+ // silent
} catch (IOException e) {
e.printStackTrace();
}
try {
ByteBuffer buffer = ByteBuffer.allocate(inBufferSize);
while (channel.isConnected()) {
- if (inChannel.read(buffer) < 0)
+ if (inChannel.read(buffer) < 0) {
+ System.err.println("in EOF");
+ channel.shutdownOutput();
break;
+ }
// int b = (int) buffer.get(0);
// if (b == 0x1B) {
// System.out.println("Ctrl+C");
} catch (IOException e) {
e.printStackTrace();
}
- if (inChannel != null)
- forwardThread.interrupt();
- readThread.interrupt();
+// if (inChannel != null)
+// forwardThread.interrupt();
+// readThread.interrupt();
}
public void setInputStream(InputStream in) {