diff --git a/G-Earth/pom.xml b/G-Earth/pom.xml
index fb8e3c7..8627241 100644
--- a/G-Earth/pom.xml
+++ b/G-Earth/pom.xml
@@ -164,6 +164,14 @@
slf4j-jdk14
2.0.0-alpha0
+
+
+ org.apache.maven
+ maven-artifact
+ 3.6.3
+
+
+
diff --git a/G-Earth/src/main/java/gearth/services/gpython/GPythonShell.java b/G-Earth/src/main/java/gearth/services/gpython/GPythonShell.java
index 204d848..bb6cc21 100644
--- a/G-Earth/src/main/java/gearth/services/gpython/GPythonShell.java
+++ b/G-Earth/src/main/java/gearth/services/gpython/GPythonShell.java
@@ -1,9 +1,165 @@
package gearth.services.gpython;
+import gearth.Main;
+import gearth.ui.extra.ExtraController;
+import javafx.application.Platform;
+import javafx.scene.control.Alert;
+import javafx.scene.control.ButtonType;
+import javafx.scene.control.Hyperlink;
+import javafx.scene.control.Label;
+import javafx.scene.layout.FlowPane;
+import javafx.scene.layout.Region;
+
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.util.ArrayList;
+import java.util.List;
+
public class GPythonShell {
- public void launch() {
+ private final String extensionName;
+ private final String cookie;
+ public GPythonShell(String extensionName, String cookie) {
+ this.extensionName = extensionName;
+ this.cookie = cookie;
+ }
+
+ public void launch(OnQtConsoleLaunch onLaunch) {
+ new Thread(() -> {
+ try {
+ // launch the jupyter console
+ ProcessBuilder gConsoleBuilder = new ProcessBuilder("python", "-m", "jupyter", "console", "--simple-prompt");
+ Process gConsole = gConsoleBuilder.start();
+
+ InputStreamReader in = new InputStreamReader(gConsole.getInputStream());
+ BufferedWriter out = new BufferedWriter(new OutputStreamWriter(gConsole.getOutputStream()));
+
+ readUntilExpectInput(in);
+
+ // obtain jupyter kernel name
+ List sysargs = enterCommandAndAwait(out, in, "import sys; sys.argv");
+ String kernelName = extractKernelName(sysargs);
+
+
+ onLaunch.launched(false);
+ }
+ catch (Exception e) {
+ e.printStackTrace();
+ showError();
+ onLaunch.launched(true);
+ }
+ }).start();
+ }
+
+ private List readUntilExpectInput(InputStreamReader in) throws IOException {
+ List readings = new ArrayList<>();
+ StringBuilder builder = new StringBuilder();
+
+ int c;
+ while ((c = in.read()) != -1) {
+ char character = (char)c;
+ if (character == '\n') {
+ String reading = builder.toString();
+ if (reading.endsWith("\r")) {
+ reading = reading.substring(0, reading.length() - 1);
+ }
+ if (reading.matches("Out\\[[0-9+]]: .*")) {
+ reading = reading.replaceAll("^Out\\[[0-9+]]: ", "");
+ if (reading.equals("")) {
+ builder = new StringBuilder();
+ continue;
+ }
+ }
+
+ readings.add(reading);
+ builder = new StringBuilder();
+ }
+ else {
+ builder.append(character);
+
+ if (builder.toString().matches("In \\[[0-9]+]: ") && !in.ready()) {
+ return readings;
+ }
+ }
+ }
+ return null;
+ }
+ private void enterCommand(BufferedWriter out, String command) throws IOException {
+ out.write(command);
+ out.newLine();
+ out.flush();
+ }
+ private List enterCommandAndAwait(BufferedWriter out, InputStreamReader in, String command) throws IOException {
+ enterCommand(out, command);
+ return readUntilExpectInput(in);
+ }
+
+ private String extractKernelName(List inputArgs) {
+ // null if not found
+
+ String joined = String.join("", inputArgs);
+ String paramsFull = joined.replaceAll("^[^\\[]*\\[", "").replaceAll("][^]]*$", "");
+ List params = new ArrayList<>();
+
+ boolean backslashed = false;
+ int beginParameterIndex = -1;
+ for (int i = 0; i < paramsFull.length(); i++) {
+ char c = paramsFull.charAt(i);
+ if (c == '\'' && !backslashed) {
+ if (beginParameterIndex == -1) {
+ beginParameterIndex = i + 1;
+ }
+ else {
+ params.add(paramsFull.substring(beginParameterIndex, i));
+ beginParameterIndex = -1;
+ }
+ }
+
+ backslashed = c == '\\' && !backslashed;
+ }
+
+ for (int i = 0; i < params.size() - 1; i++) {
+ if (params.get(i).equals("-f")) {
+ return params.get(i+1);
+ }
+ }
+ return null;
+ }
+
+ private void showError() {
+ Platform.runLater(() -> {
+ Alert alert = new Alert(Alert.AlertType.ERROR, "G-Python error", ButtonType.OK);
+ alert.setTitle("G-Python error");
+
+ FlowPane fp = new FlowPane();
+ Label lbl = new Label("Something went wrong launching the G-Python shell," +
+ System.lineSeparator() + "are you sure you followed the installation guide correctly?" +
+ System.lineSeparator() + System.lineSeparator() + "More information here:");
+ Hyperlink link = new Hyperlink(ExtraController.INFO_URL_GPYTHON);
+ fp.getChildren().addAll(lbl, link);
+ link.setOnAction(event -> {
+ Main.main.getHostServices().showDocument(link.getText());
+ event.consume();
+ });
+
+ alert.getDialogPane().setMinHeight(Region.USE_PREF_SIZE);
+ alert.getDialogPane().setContent(fp);
+ alert.show();
+ });
+ }
+
+ public String getExtensionName() {
+ return extensionName;
+ }
+
+ public static void main(String[] args) throws IOException {
+ GPythonShell shell = new GPythonShell("test", "cookie");
+ shell.launch((b) -> {
+ System.out.println("launched");
+ });
}
}
diff --git a/G-Earth/src/main/java/gearth/services/gpython/GPythonVersionUtils.java b/G-Earth/src/main/java/gearth/services/gpython/GPythonVersionUtils.java
new file mode 100644
index 0000000..9d2f3bf
--- /dev/null
+++ b/G-Earth/src/main/java/gearth/services/gpython/GPythonVersionUtils.java
@@ -0,0 +1,96 @@
+package gearth.services.gpython;
+
+import org.apache.maven.artifact.versioning.ComparableVersion;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.List;
+
+public class GPythonVersionUtils {
+
+ private static final String MIN_GPYTHON_VERSION = "0.1";
+ private static final String MIN_PYTHON_VERSION = "3.2";
+
+ // returns null if python not installed
+ public static String pythonVersion() {
+ List commandOutput = executeCommand("python", "--version");
+ if (commandOutput.size() == 0) {
+ return null;
+ }
+ String maybeVersion = commandOutput.get(0);
+ if (!maybeVersion.contains("Python")) {
+ return null;
+ }
+
+ return maybeVersion.split(" ")[1];
+ }
+
+ public static boolean validInstallation() {
+ // validates if user has all dependencies installed
+ String pythonVersion = pythonVersion();
+ if (pythonVersion == null) {
+ return false;
+ }
+
+ ComparableVersion version = new ComparableVersion(pythonVersion);
+ if (version.compareTo(new ComparableVersion(MIN_PYTHON_VERSION)) < 0) {
+ return false;
+ }
+
+ List allPackages = executeCommand("python", "-m", "pip", "list");
+ allPackages = allPackages.subList(2, allPackages.size());
+
+ String qtconsole = getPackageVersion(allPackages, "qtconsole");
+ String pyqt5 = getPackageVersion(allPackages, "pyqt5");
+ String jupyterConsole = getPackageVersion(allPackages, "jupyter-console");
+ String gPython = getPackageVersion(allPackages, "g-python");
+
+ if (qtconsole == null || pyqt5 == null || jupyterConsole == null || gPython == null) {
+ return false;
+ }
+
+ ComparableVersion gVersion = new ComparableVersion(gPython);
+ return gVersion.compareTo(new ComparableVersion(MIN_GPYTHON_VERSION)) >= 0;
+ }
+
+ // null if not found
+ private static String getPackageVersion(List allPackages, String pkg) {
+ pkg = pkg.toLowerCase();
+
+ for (String maybePkg : allPackages) {
+ String[] split = maybePkg.split(" +");
+ if (split[0].toLowerCase().equals(pkg)) {
+ return split[1];
+ }
+ }
+ return null;
+ }
+
+ private static List executeCommand(String... command) {
+ List output = new ArrayList<>();
+
+ try {
+ ProcessBuilder processBuilder = new ProcessBuilder(command);
+ Process process = processBuilder.start();
+ BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
+
+ String line;
+ while ((line = reader.readLine()) != null) {
+ output.add(line);
+ }
+
+ process.waitFor();
+
+ } catch (IOException | InterruptedException e) {
+ e.printStackTrace();
+ }
+ return output;
+ }
+
+// public static void main(String[] args) {
+// System.out.println(validInstallation());
+// }
+
+}
diff --git a/G-Earth/src/main/java/gearth/services/gpython/JupyterConsole.java b/G-Earth/src/main/java/gearth/services/gpython/JupyterConsole.java
deleted file mode 100644
index cf36fa0..0000000
--- a/G-Earth/src/main/java/gearth/services/gpython/JupyterConsole.java
+++ /dev/null
@@ -1,4 +0,0 @@
-package gearth.services.gpython;
-
-public class JupyterConsole {
-}
diff --git a/G-Earth/src/main/java/gearth/services/gpython/OnQtConsoleLaunch.java b/G-Earth/src/main/java/gearth/services/gpython/OnQtConsoleLaunch.java
new file mode 100644
index 0000000..8414672
--- /dev/null
+++ b/G-Earth/src/main/java/gearth/services/gpython/OnQtConsoleLaunch.java
@@ -0,0 +1,7 @@
+package gearth.services.gpython;
+
+public interface OnQtConsoleLaunch {
+
+ void launched(boolean failed);
+
+}
diff --git a/G-Earth/src/main/java/gearth/services/gpython/PythonUtils.java b/G-Earth/src/main/java/gearth/services/gpython/PythonUtils.java
deleted file mode 100644
index 92c7787..0000000
--- a/G-Earth/src/main/java/gearth/services/gpython/PythonUtils.java
+++ /dev/null
@@ -1,4 +0,0 @@
-package gearth.services.gpython;
-
-public class PythonUtils {
-}
diff --git a/G-Earth/src/main/java/gearth/ui/extensions/ExtensionsController.java b/G-Earth/src/main/java/gearth/ui/extensions/ExtensionsController.java
index ba43241..098207b 100644
--- a/G-Earth/src/main/java/gearth/ui/extensions/ExtensionsController.java
+++ b/G-Earth/src/main/java/gearth/ui/extensions/ExtensionsController.java
@@ -1,5 +1,6 @@
package gearth.ui.extensions;
+import gearth.Main;
import gearth.services.extensionhandler.ExtensionConnectedListener;
import gearth.services.extensionhandler.ExtensionHandler;
import gearth.services.extensionhandler.extensions.ExtensionListener;
@@ -9,18 +10,21 @@ import gearth.services.extensionhandler.extensions.implementations.network.execu
import gearth.services.extensionhandler.extensions.implementations.network.executer.ExtensionRunner;
import gearth.services.extensionhandler.extensions.implementations.network.executer.ExtensionRunnerFactory;
import gearth.services.gpython.GPythonShell;
+import gearth.services.gpython.OnQtConsoleLaunch;
import gearth.ui.SubForm;
import gearth.ui.extensions.logger.ExtensionLogger;
+import gearth.ui.extra.ExtraController;
import javafx.application.Platform;
import javafx.event.ActionEvent;
-import javafx.scene.control.Button;
-import javafx.scene.control.ScrollPane;
-import javafx.scene.control.TextField;
+import javafx.scene.control.*;
+import javafx.scene.layout.FlowPane;
import javafx.scene.layout.GridPane;
+import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import javafx.stage.FileChooser;
import java.io.File;
+import java.io.IOException;
/**
* Created by Jonas on 06/04/18.
@@ -103,10 +107,24 @@ public class ExtensionsController extends SubForm {
@Override
protected void onTabOpened() {
- btn_gpython.setDisable(!parentController.extraController.useGPython());
+ updateGPythonStatus();
}
+ public void updateGPythonStatus() {
+ if (!pythonShellLaunching) {
+ btn_gpython.setDisable(!parentController.extraController.useGPython());
+ }
+ }
+
+
+ private volatile boolean pythonShellLaunching = false;
public void gpythonBtnClicked(ActionEvent actionEvent) {
- new GPythonShell().launch();
+ pythonShellLaunching = true;
+ Platform.runLater(() -> btn_gpython.setDisable(true));
+ GPythonShell shell = new GPythonShell("test", "cookie");
+ shell.launch((b) -> {
+ pythonShellLaunching = false;
+ Platform.runLater(this::updateGPythonStatus);
+ });
}
}
diff --git a/G-Earth/src/main/java/gearth/ui/extra/ExtraController.java b/G-Earth/src/main/java/gearth/ui/extra/ExtraController.java
index 69148a3..1fa67f0 100644
--- a/G-Earth/src/main/java/gearth/ui/extra/ExtraController.java
+++ b/G-Earth/src/main/java/gearth/ui/extra/ExtraController.java
@@ -1,15 +1,21 @@
package gearth.ui.extra;
+import gearth.Main;
import gearth.misc.Cacher;
import gearth.protocol.HConnection;
import gearth.protocol.connection.HState;
import gearth.protocol.connection.proxy.ProxyProviderFactory;
import gearth.protocol.connection.proxy.SocksConfiguration;
+import gearth.services.gpython.GPythonVersionUtils;
import gearth.ui.SubForm;
import gearth.ui.info.InfoController;
+import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.scene.control.*;
+import javafx.scene.layout.FlowPane;
import javafx.scene.layout.GridPane;
+import javafx.scene.layout.Region;
+import javafx.scene.web.WebView;
import org.json.JSONObject;
/**
@@ -17,6 +23,8 @@ import org.json.JSONObject;
*/
public class ExtraController extends SubForm implements SocksConfiguration {
+ public static final String INFO_URL_GPYTHON = "www.placeholder.com";
+
public static final String NOTEPAD_CACHE_KEY = "notepad_text";
public static final String SOCKS_CACHE_KEY = "socks_config";
public static final String GPYTHON_CACHE_KEY = "use_gpython";
@@ -143,4 +151,47 @@ public class ExtraController extends SubForm implements SocksConfiguration {
public boolean useGPython() {
return cbx_gpython.isSelected();
}
+
+ public void gpythonCbxClick(ActionEvent actionEvent) {
+ if (cbx_gpython.isSelected()) {
+ new Thread(() -> {
+ Platform.runLater(() -> {
+ cbx_gpython.setSelected(false);
+ cbx_gpython.setDisable(true);
+ });
+ if (!GPythonVersionUtils.validInstallation()) {
+ Platform.runLater(() -> {
+ Alert alert = new Alert(Alert.AlertType.ERROR, "G-Python installation", ButtonType.OK);
+ alert.setTitle("G-Python installation");
+
+ FlowPane fp = new FlowPane();
+ Label lbl = new Label("Before using G-Python, install the right packages using pip!" +
+ System.lineSeparator() + System.lineSeparator() + "More information here:");
+ Hyperlink link = new Hyperlink(INFO_URL_GPYTHON);
+ fp.getChildren().addAll( lbl, link);
+ link.setOnAction(event -> {
+ Main.main.getHostServices().showDocument(link.getText());
+ event.consume();
+ });
+
+ alert.getDialogPane().setMinHeight(Region.USE_PREF_SIZE);
+ alert.getDialogPane().setContent(fp);
+ alert.show();
+
+ cbx_gpython.setDisable(false);
+ });
+ }
+ else {
+ Platform.runLater(() -> {
+ cbx_gpython.setSelected(true);
+ cbx_gpython.setDisable(false);
+ parentController.extensionsController.updateGPythonStatus();
+ });
+ }
+ }).start();
+
+
+ }
+
+ }
}
diff --git a/G-Earth/src/main/resources/gearth/ui/extra/Extra.fxml b/G-Earth/src/main/resources/gearth/ui/extra/Extra.fxml
index ff6adad..fac9d86 100644
--- a/G-Earth/src/main/resources/gearth/ui/extra/Extra.fxml
+++ b/G-Earth/src/main/resources/gearth/ui/extra/Extra.fxml
@@ -112,7 +112,7 @@
-
+