From 11303a181124aead260a1405a9072d52d7636bef Mon Sep 17 00:00:00 2001
From: tobias <thinkdifferent055@gmail.com>
Date: Tue, 17 Nov 2020 14:29:10 +0100
Subject: [PATCH] Fixed #161 - Add WebAPI port settings

---
 .../java/de/tobias/playpad/PlayPadImpl.java   | 37 +++++++--
 .../global/GlobalSettingsViewController.java  | 12 ++-
 .../ProfileSettingsViewController.java        | 15 +++-
 .../main/java/de/tobias/playpad/PlayPad.java  | 14 ++++
 .../websocket/settings/WebApiSettings.java    | 28 +++++++
 .../plugin/webapi/lang/base.properties        |  0
 .../plugin/webapi/lang/base_de.properties     |  5 ++
 .../plugin/webapi/view/WebApiSettings.fxml    | 33 ++++++++
 .../playpad/plugin/api/WebApiPlugin.scala     | 39 +++++++--
 .../WebApiSettingsViewController.scala        | 81 +++++++++++++++++++
 10 files changed, 245 insertions(+), 19 deletions(-)
 create mode 100644 PlayWallPlugins/PlayWallPluginWebAPI/src/main/java/de/tobias/playpad/plugin/api/websocket/settings/WebApiSettings.java
 create mode 100644 PlayWallPlugins/PlayWallPluginWebAPI/src/main/resources/plugin/webapi/lang/base.properties
 create mode 100644 PlayWallPlugins/PlayWallPluginWebAPI/src/main/resources/plugin/webapi/lang/base_de.properties
 create mode 100644 PlayWallPlugins/PlayWallPluginWebAPI/src/main/resources/plugin/webapi/view/WebApiSettings.fxml
 create mode 100644 PlayWallPlugins/PlayWallPluginWebAPI/src/main/scala/de/tobias/playpad/plugin/api/websocket/settings/WebApiSettingsViewController.scala

diff --git a/PlayWall/src/main/java/de/tobias/playpad/PlayPadImpl.java b/PlayWall/src/main/java/de/tobias/playpad/PlayPadImpl.java
index f3f434f5..a8590e8e 100644
--- a/PlayWall/src/main/java/de/tobias/playpad/PlayPadImpl.java
+++ b/PlayWall/src/main/java/de/tobias/playpad/PlayPadImpl.java
@@ -26,6 +26,8 @@ import de.tobias.playpad.viewcontroller.dialog.project.ProjectLoadDialog;
 import de.tobias.playpad.viewcontroller.dialog.project.ProjectReaderDelegateImpl;
 import de.tobias.playpad.viewcontroller.main.IMainViewController;
 import de.tobias.playpad.viewcontroller.main.MainViewController;
+import de.tobias.playpad.viewcontroller.option.GlobalSettingsTabViewController;
+import de.tobias.playpad.viewcontroller.option.ProfileSettingsTabViewController;
 import javafx.application.Application;
 import javafx.application.Platform;
 import javafx.scene.image.Image;
@@ -37,21 +39,24 @@ import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.function.Consumer;
+import java.util.function.Supplier;
 
 public class PlayPadImpl implements PlayPad {
 
-	private Application.Parameters parameters;
+	private final Application.Parameters parameters;
 
-	private List<MainWindowListener> mainViewListeners = new ArrayList<>();
-	private List<SettingsListener> settingsListeners = new ArrayList<>();
-	private List<PadListener> padListeners = new ArrayList<>();
-	private List<GlobalListener> globalListeners = new ArrayList<>();
+	private final List<MainWindowListener> mainViewListeners = new ArrayList<>();
+	private final List<SettingsListener> settingsListeners = new ArrayList<>();
+	private final List<PadListener> padListeners = new ArrayList<>();
+	private final List<GlobalListener> globalListeners = new ArrayList<>();
+	private final List<Supplier<ProfileSettingsTabViewController>> additionalProfileSettingsTabs = new ArrayList<>();
+	private final List<Supplier<GlobalSettingsTabViewController>> additionalGlobalSettingsTabs = new ArrayList<>();
 
 	private MainViewController mainViewController;
 	private Image stageIcon;
 	private Project currentProject;
 
-	private Module module;
+	private final Module module;
 
 	private UpdateService updateService;
 	protected GlobalSettings globalSettings;
@@ -242,6 +247,26 @@ public class PlayPadImpl implements PlayPad {
 		return updateService;
 	}
 
+	@Override
+	public void addAdditionalProfileSettingsTab(Supplier<ProfileSettingsTabViewController> tab) {
+		additionalProfileSettingsTabs.add(tab);
+	}
+
+	@Override
+	public List<Supplier<ProfileSettingsTabViewController>> getAdditionalProfileSettingsTabs() {
+		return additionalProfileSettingsTabs;
+	}
+
+	@Override
+	public void addGlobalSettingsTab(Supplier<GlobalSettingsTabViewController> tab) {
+		additionalGlobalSettingsTabs.add(tab);
+	}
+
+	@Override
+	public List<Supplier<GlobalSettingsTabViewController>> getGlobalSettingsTabs() {
+		return additionalGlobalSettingsTabs;
+	}
+
 	/*
 	Getter / Setter
 	 */
diff --git a/PlayWall/src/main/java/de/tobias/playpad/viewcontroller/option/global/GlobalSettingsViewController.java b/PlayWall/src/main/java/de/tobias/playpad/viewcontroller/option/global/GlobalSettingsViewController.java
index 068ecda3..e29db503 100644
--- a/PlayWall/src/main/java/de/tobias/playpad/viewcontroller/option/global/GlobalSettingsViewController.java
+++ b/PlayWall/src/main/java/de/tobias/playpad/viewcontroller/option/global/GlobalSettingsViewController.java
@@ -36,9 +36,8 @@ public class GlobalSettingsViewController extends NVC implements IGlobalSettings
 	@FXML
 	private Button finishButton;
 
-	private List<GlobalSettingsTabViewController> tabs = new ArrayList<>();
-
-	private Runnable onFinish;
+	private final List<GlobalSettingsTabViewController> tabs = new ArrayList<>();
+	private final Runnable onFinish;
 
 	public GlobalSettingsViewController(Window owner, Runnable onFinish) {
 		load("view/option/global", "GlobalSettingsView", Localization.getBundle());
@@ -53,6 +52,13 @@ public class GlobalSettingsViewController extends NVC implements IGlobalSettings
 		addTab(new KeysTabViewController());
 		addTab(new UpdateTabViewController());
 
+		PlayPadPlugin.getInstance().getGlobalSettingsTabs().forEach(supplier -> {
+			final GlobalSettingsTabViewController globalSettingsTabViewController = supplier.get();
+			if (globalSettingsTabViewController != null) {
+				addTab(globalSettingsTabViewController);
+			}
+		});
+
 		// Show Current Settings
 		loadTabs();
 	}
diff --git a/PlayWall/src/main/java/de/tobias/playpad/viewcontroller/option/profile/ProfileSettingsViewController.java b/PlayWall/src/main/java/de/tobias/playpad/viewcontroller/option/profile/ProfileSettingsViewController.java
index 1c4b3641..4f0ab953 100644
--- a/PlayWall/src/main/java/de/tobias/playpad/viewcontroller/option/profile/ProfileSettingsViewController.java
+++ b/PlayWall/src/main/java/de/tobias/playpad/viewcontroller/option/profile/ProfileSettingsViewController.java
@@ -40,15 +40,15 @@ public class ProfileSettingsViewController extends NVC implements IProfileSettin
 	@FXML
 	private Button finishButton;
 
-	private List<ProfileSettingsTabViewController> tabs = new ArrayList<>();
+	private final List<ProfileSettingsTabViewController> tabs = new ArrayList<>();
 
-	private Runnable onFinish;
+	private final Runnable onFinish;
 
 	public ProfileSettingsViewController(Window owner, Project project, Runnable onFinish) {
 		load("view/option/profile", "SettingsView", Localization.getBundle());
 		this.onFinish = onFinish;
 
-		boolean activePlayer = project.hasActivePlayers();
+		boolean hasActivePlayers = project.hasActivePlayers();
 
 		addTab(new MappingTabViewController());
 		addTab(new DesignTabViewController());
@@ -58,12 +58,19 @@ public class ProfileSettingsViewController extends NVC implements IProfileSettin
 		PadContentRegistry padContents = PlayPadPlugin.getRegistries().getPadContents();
 		for (String type : padContents.getTypes()) {
 			PadContentFactory component = padContents.getFactory(type);
-			ProfileSettingsTabViewController controller = component.getSettingsTabViewController(activePlayer);
+			ProfileSettingsTabViewController controller = component.getSettingsTabViewController(hasActivePlayers);
 			if (controller != null) {
 				addTab(controller);
 			}
 		}
 
+		PlayPadPlugin.getInstance().getAdditionalProfileSettingsTabs().forEach(supplier -> {
+			ProfileSettingsTabViewController controller = supplier.get();
+			if (controller != null) {
+				addTab(controller);
+			}
+		});
+
 		NVCStage nvcStage = applyViewControllerToStage();
 		nvcStage.initOwner(owner);
 		nvcStage.addCloseHook(this::onFinish);
diff --git a/PlayWallCore/src/main/java/de/tobias/playpad/PlayPad.java b/PlayWallCore/src/main/java/de/tobias/playpad/PlayPad.java
index 14d50ffa..c480828c 100644
--- a/PlayWallCore/src/main/java/de/tobias/playpad/PlayPad.java
+++ b/PlayWallCore/src/main/java/de/tobias/playpad/PlayPad.java
@@ -14,12 +14,15 @@ import de.tobias.playpad.project.ProjectReader.ProjectReaderDelegate.ProfileAbor
 import de.tobias.playpad.project.ref.ProjectReference;
 import de.tobias.playpad.settings.GlobalSettings;
 import de.tobias.playpad.viewcontroller.main.IMainViewController;
+import de.tobias.playpad.viewcontroller.option.GlobalSettingsTabViewController;
+import de.tobias.playpad.viewcontroller.option.ProfileSettingsTabViewController;
 import javafx.scene.image.Image;
 import org.dom4j.DocumentException;
 
 import java.io.IOException;
 import java.util.List;
 import java.util.function.Consumer;
+import java.util.function.Supplier;
 
 /**
  * Hauptfunktionen für Listener und zur Programmsteuerung für Plugins.
@@ -138,4 +141,15 @@ public interface PlayPad {
 
 	UpdateService getUpdateService();
 
+	/*
+	Settings Views
+	 */
+
+	void addAdditionalProfileSettingsTab(Supplier<ProfileSettingsTabViewController> tab);
+
+	List<Supplier<ProfileSettingsTabViewController>> getAdditionalProfileSettingsTabs();
+
+	void addGlobalSettingsTab(Supplier<GlobalSettingsTabViewController> tab);
+
+	List<Supplier<GlobalSettingsTabViewController>> getGlobalSettingsTabs();
 }
diff --git a/PlayWallPlugins/PlayWallPluginWebAPI/src/main/java/de/tobias/playpad/plugin/api/websocket/settings/WebApiSettings.java b/PlayWallPlugins/PlayWallPluginWebAPI/src/main/java/de/tobias/playpad/plugin/api/websocket/settings/WebApiSettings.java
new file mode 100644
index 00000000..c5b35f5f
--- /dev/null
+++ b/PlayWallPlugins/PlayWallPluginWebAPI/src/main/java/de/tobias/playpad/plugin/api/websocket/settings/WebApiSettings.java
@@ -0,0 +1,28 @@
+package de.tobias.playpad.plugin.api.websocket.settings;
+
+import de.thecodelabs.storage.settings.annotation.FilePath;
+import de.thecodelabs.storage.settings.annotation.Key;
+
+@FilePath("webapi.json")
+public class WebApiSettings {
+	@Key
+	private boolean enabled = false;
+	@Key
+	private int port = 9876;
+
+	public boolean isEnabled() {
+		return enabled;
+	}
+
+	public void setEnabled(boolean enabled) {
+		this.enabled = enabled;
+	}
+
+	public int getPort() {
+		return port;
+	}
+
+	public void setPort(int port) {
+		this.port = port;
+	}
+}
diff --git a/PlayWallPlugins/PlayWallPluginWebAPI/src/main/resources/plugin/webapi/lang/base.properties b/PlayWallPlugins/PlayWallPluginWebAPI/src/main/resources/plugin/webapi/lang/base.properties
new file mode 100644
index 00000000..e69de29b
diff --git a/PlayWallPlugins/PlayWallPluginWebAPI/src/main/resources/plugin/webapi/lang/base_de.properties b/PlayWallPlugins/PlayWallPluginWebAPI/src/main/resources/plugin/webapi/lang/base_de.properties
new file mode 100644
index 00000000..e38784c8
--- /dev/null
+++ b/PlayWallPlugins/PlayWallPluginWebAPI/src/main/resources/plugin/webapi/lang/base_de.properties
@@ -0,0 +1,5 @@
+webapi.settings=WebAPI
+webapi.settings.enable=WebAPI aktiv
+webapi.settings.enabled=Aktiv
+webapi.settings.port=Port
+webapi.settings.restart=\u00C4nderungen an den Einstellungen werden erst mit einem Neustart von PlayWall wirksam.
\ No newline at end of file
diff --git a/PlayWallPlugins/PlayWallPluginWebAPI/src/main/resources/plugin/webapi/view/WebApiSettings.fxml b/PlayWallPlugins/PlayWallPluginWebAPI/src/main/resources/plugin/webapi/view/WebApiSettings.fxml
new file mode 100644
index 00000000..473dba8d
--- /dev/null
+++ b/PlayWallPlugins/PlayWallPluginWebAPI/src/main/resources/plugin/webapi/view/WebApiSettings.fxml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<?import de.thecodelabs.utils.ui.scene.input.NumberTextField?>
+<?import javafx.geometry.Insets?>
+<?import javafx.scene.control.CheckBox?>
+<?import javafx.scene.control.Label?>
+<?import javafx.scene.layout.HBox?>
+<?import javafx.scene.layout.VBox?>
+<VBox maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" spacing="14.0"
+      xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
+    <children>
+        <HBox alignment="CENTER_LEFT" spacing="14.0">
+            <children>
+                <Label alignment="CENTER_RIGHT" prefWidth="150.0" text="%webapi.settings.enable"/>
+                <CheckBox fx:id="activeCheckbox" mnemonicParsing="false" text="%webapi.settings.enabled"/>
+            </children>
+        </HBox>
+        <HBox alignment="CENTER_LEFT" spacing="14.0">
+            <children>
+                <Label alignment="CENTER_RIGHT" prefWidth="150.0" text="%webapi.settings.port"/>
+                <NumberTextField fx:id="portTextField"/>
+            </children>
+        </HBox>
+        <Label text="%webapi.settings.restart">
+            <padding>
+                <Insets left="164.0"/>
+            </padding>
+        </Label>
+    </children>
+    <padding>
+        <Insets bottom="14.0" left="14.0" right="14.0" top="14.0"/>
+    </padding>
+</VBox>
diff --git a/PlayWallPlugins/PlayWallPluginWebAPI/src/main/scala/de/tobias/playpad/plugin/api/WebApiPlugin.scala b/PlayWallPlugins/PlayWallPluginWebAPI/src/main/scala/de/tobias/playpad/plugin/api/WebApiPlugin.scala
index b48693f9..0d4d7321 100644
--- a/PlayWallPlugins/PlayWallPluginWebAPI/src/main/scala/de/tobias/playpad/plugin/api/WebApiPlugin.scala
+++ b/PlayWallPlugins/PlayWallPluginWebAPI/src/main/scala/de/tobias/playpad/plugin/api/WebApiPlugin.scala
@@ -1,18 +1,27 @@
 package de.tobias.playpad.plugin.api
 
+import java.nio.file.{Files, Path}
+
 import de.thecodelabs.logger.Logger
 import de.thecodelabs.plugins.PluginDescriptor
 import de.thecodelabs.plugins.versionizer.PluginArtifact
+import de.thecodelabs.storage.settings.{Storage, StorageTypes}
+import de.thecodelabs.utils.application.ApplicationUtils
+import de.thecodelabs.utils.application.container.PathType
+import de.thecodelabs.utils.util.Localization
 import de.tobias.playpad.PlayPadPlugin
 import de.tobias.playpad.plugin.api.websocket.WebSocketHandler
 import de.tobias.playpad.plugin.api.websocket.listener.{PadStatusListener, ProjectListener}
+import de.tobias.playpad.plugin.api.websocket.settings.{WebApiSettings, WebApiSettingsViewController}
 import de.tobias.playpad.plugin.{Module, PlayPadPluginStub}
-import spark.Spark
+import spark.{Request, Response, Spark}
 
 class WebApiPlugin extends PlayPadPluginStub with PluginArtifact {
 
 	private var module: Module = _
 
+	private var webApiSettings: WebApiSettings = _
+
 	override def startup(descriptor: PluginDescriptor): Unit = {
 		module = new Module(descriptor.getName, descriptor.getArtifactId)
 
@@ -21,16 +30,34 @@ class WebApiPlugin extends PlayPadPluginStub with PluginArtifact {
 
 		Logger.debug("Enable Web API Plugin")
 
-		Spark.port(9876)
-		Spark.webSocket("/api", WebSocketHandler.instance)
-		Spark.get("/", (_, _) => "PlayWall API")
+		val settingsPath = WebApiPlugin.getWebApiSettingsPath
+		if (Files.exists(settingsPath)) {
+			webApiSettings = Storage.load(StorageTypes.JSON, classOf[WebApiSettings])
+		}
+		if (webApiSettings == null) {
+			webApiSettings = new WebApiSettings
+		}
+
+		if (webApiSettings.isEnabled) {
+			Spark.port(webApiSettings.getPort)
+			Spark.webSocket("/api", WebSocketHandler.instance)
+			Spark.get("/", (_: Request, _: Response) => "PlayWall API")
+			Logger.info(f"Start WebAPI on port ${webApiSettings.getPort}")
+		}
+
+		PlayPadPlugin.getInstance().addGlobalSettingsTab(() => new WebApiSettingsViewController(webApiSettings))
+		Localization.addResourceBundle("plugin/webapi/lang/base", getClass.getClassLoader)
 	}
 
 	override def shutdown(): Unit = {
-		Logger.debug("Disable Web API Plugin")
-
 		Spark.stop()
+		Logger.debug("Disable Web API Plugin")
 	}
 
 	override def getModule: Module = module
+
 }
+
+object WebApiPlugin {
+	def getWebApiSettingsPath: Path = ApplicationUtils.getApplication.getPath(PathType.CONFIGURATION, "webapi.json")
+}
\ No newline at end of file
diff --git a/PlayWallPlugins/PlayWallPluginWebAPI/src/main/scala/de/tobias/playpad/plugin/api/websocket/settings/WebApiSettingsViewController.scala b/PlayWallPlugins/PlayWallPluginWebAPI/src/main/scala/de/tobias/playpad/plugin/api/websocket/settings/WebApiSettingsViewController.scala
new file mode 100644
index 00000000..4f86d86d
--- /dev/null
+++ b/PlayWallPlugins/PlayWallPluginWebAPI/src/main/scala/de/tobias/playpad/plugin/api/websocket/settings/WebApiSettingsViewController.scala
@@ -0,0 +1,81 @@
+package de.tobias.playpad.plugin.api.websocket.settings
+
+import java.nio.file.Files
+
+import de.thecodelabs.logger.Logger
+import de.thecodelabs.storage.settings.{Storage, StorageTypes}
+import de.thecodelabs.utils.ui.scene.input.NumberTextField
+import de.thecodelabs.utils.util.Localization
+import de.tobias.playpad.plugin.api.WebApiPlugin
+import de.tobias.playpad.settings.GlobalSettings
+import de.tobias.playpad.viewcontroller.option.GlobalSettingsTabViewController
+import javafx.fxml.FXML
+import javafx.scene.control.CheckBox
+
+class WebApiSettingsViewController(val webApiSettings: WebApiSettings) extends GlobalSettingsTabViewController {
+
+	load("plugin/webapi/view", "WebApiSettings", Localization.getBundle)
+
+	@FXML
+	var activeCheckbox: CheckBox = _
+
+	@FXML
+	var portTextField: NumberTextField = _
+
+	/**
+	 * Lädt alle Einstellungen vom Model in die GUI.
+	 *
+	 * @param settings Aktuelles GlobalSettings
+	 */
+	override def loadSettings(settings: GlobalSettings): Unit = {
+		activeCheckbox.setSelected(webApiSettings.isEnabled)
+		portTextField.setText(webApiSettings.getPort.toString)
+	}
+
+	/**
+	 * Speichert alle Änderungen in das Model.
+	 *
+	 * @param settings Aktuelles GlobalSettings
+	 */
+	override def saveSettings(settings: GlobalSettings): Unit = {
+		webApiSettings.setEnabled(activeCheckbox.isSelected)
+		webApiSettings.setPort(portTextField.getText.toInt)
+
+		saveToFile()
+	}
+
+	/**
+	 * Gibt <code>true</code> zurück, wenn im Hauptprogramm etwas neu geladen werden muss.
+	 *
+	 * @return <code>true</code> Benötigt Reload
+	 */
+	override def needReload(): Boolean = {
+		false
+	}
+
+	/**
+	 * Prüft ob die eingetragen Einstellungen erlaubt sind. Bei falschen Eingaben können die Einstellungen nicht
+	 * geschlossen werden.
+	 *
+	 * @return <code>true</code> Einstellungen erlaubt. <code>false</code> Einstellungen fehlerhaft.
+	 */
+	override def validSettings(): Boolean = {
+		true
+	}
+
+	/**
+	 * Gibt den Namen für den Tab zurück.
+	 *
+	 * @return Display Name des Tabs.
+	 */
+	override def name(): String = Localization.getString("webapi.settings")
+
+	private def saveToFile(): Unit = {
+		val settingsPath = WebApiPlugin.getWebApiSettingsPath
+		if (Files.notExists(settingsPath)) {
+			Files.createDirectories(settingsPath.getParent)
+		}
+		Storage.save(StorageTypes.JSON, webApiSettings)
+		Logger.info(f"Save WebAPI settings to $settingsPath")
+	}
+}
-- 
GitLab