/* * Copyright 2013-2014 Odysseus Software GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.musicmount.fx; import java.io.File; import java.net.MalformedURLException; import java.util.logging.Level; import java.util.logging.Logger; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import javafx.collections.FXCollections; import javafx.concurrent.Service; import javafx.concurrent.Task; import javafx.concurrent.WorkerStateEvent; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.geometry.HPos; import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.control.Button; import javafx.scene.control.CheckBox; import javafx.scene.control.ChoiceBox; import javafx.scene.control.Label; import javafx.scene.control.PasswordField; import javafx.scene.control.TextField; import javafx.scene.control.Tooltip; import javafx.scene.layout.BorderPane; import javafx.scene.layout.ColumnConstraintsBuilder; import javafx.scene.layout.GridPane; import javafx.scene.layout.HBox; import javafx.scene.layout.Pane; import javafx.scene.layout.Priority; import javafx.scene.text.Text; import javafx.stage.DirectoryChooser; import org.musicmount.tester.MusicMountTester; import org.musicmount.util.BonjourService; public class FXTestController { static final Logger LOGGER = Logger.getLogger(FXTestController.class.getName()); private static final String STATUS_NO_RELATIVE_MUSIC_PATH = "Cannot calculate relative music path, custom path path required"; private Pane pane; private TextField musicFolderTextField; private Button musicFolderChooseButton; private TextField mountFolderTextField; private Button mountFolderChooseButton; private TextField musicPathTextField; private ChoiceBox<String> musicPathChoiceBox; private TextField portTextField; private TextField userTextField; private PasswordField passwordField; private CheckBox bonjourCheckBox; private Button runButton; private Text statusText; private final BonjourService bonjourService; private final MusicMountTester tester; private final FXCommandModel model; private final Service<Object> service = new Service<Object>() { @Override protected Task<Object> createTask() { return new Task<Object>() { @Override protected Object call() throws Exception { tester.start(model.getMusicFolder(), model.getMountFolder(), model.getMusicPath(), model.getServerPort().intValue(), getUser(), getPassword()); if (bonjourService != null && model.isBonjour()) { startBonjour(); } tester.await(); return null; } @Override public boolean cancel(boolean mayInterruptIfRunning) { try { if (bonjourService != null && model.isBonjour()) { stopBonjour(); } tester.stop(); } catch (Exception e) { return false; } return true; } }; } }; public FXTestController(final FXCommandModel model) { this.model = model; this.bonjourService = createBonjour(); this.pane = createView(); tester = new MusicMountTester(); musicFolderTextField.textProperty().addListener(new ChangeListener<String>() { @Override public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) { model.setMusicFolder(model.toFolder(newValue)); if (model.getCustomMusicPath() == null) { updateMusicPath(); } updateRunButton(); } }); musicFolderChooseButton.setOnAction(new EventHandler<ActionEvent>() { @Override public void handle(ActionEvent event) { DirectoryChooser directoryChooser = new DirectoryChooser(); directoryChooser.setTitle("Music Folder"); File directory = directoryChooser.showDialog(musicFolderChooseButton.getScene().getWindow()); if (directory != null) { musicFolderTextField.setText(directory.getAbsolutePath()); } } }); mountFolderTextField.textProperty().addListener(new ChangeListener<String>() { @Override public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) { model.setMountFolder(model.toFolder(newValue)); if (model.getCustomMusicPath() == null) { updateMusicPath(); } updateRunButton(); } }); mountFolderChooseButton.setOnAction(new EventHandler<ActionEvent>() { @Override public void handle(ActionEvent event) { DirectoryChooser directoryChooser = new DirectoryChooser(); directoryChooser.setTitle("Site Folder"); File directory = directoryChooser.showDialog(mountFolderChooseButton.getScene().getWindow()); if (directory != null) { mountFolderTextField.setText(directory.getAbsolutePath()); } } }); musicPathTextField.textProperty().addListener(new ChangeListener<String>() { @Override public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) { if (model.getCustomMusicPath() != null) { model.setCustomMusicPath(newValue); } updateRunButton(); } }); musicPathChoiceBox.getSelectionModel().selectedIndexProperty().addListener(new ChangeListener<Number>() { @Override public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) { if(newValue.intValue() == 0) { // auto model.setCustomMusicPath(null); } else if (model.getCustomMusicPath() == null) { // custom model.setCustomMusicPath(FXCommandModel.DEFAULT_CUSTOM_MUSIC_PATH); } updateMusicPath(); updateRunButton(); } }); portTextField.textProperty().addListener(new ChangeListener<String>() { @Override public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) { try { model.setServerPort(Integer.valueOf(portTextField.getText())); } catch (NumberFormatException e) { model.setServerPort(null); } updateRunButton(); } }); bonjourCheckBox.setOnAction(new EventHandler<ActionEvent>() { @Override public void handle(ActionEvent event) { model.setBonjour(bonjourCheckBox.isSelected()); } }); userTextField.textProperty().addListener(new ChangeListener<String>() { @Override public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) { updateUserAndPassword(); updateRunButton(); } }); passwordField.textProperty().addListener(new ChangeListener<String>() { @Override public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) { updateUserAndPassword(); updateRunButton(); } }); service.setOnRunning(new EventHandler<WorkerStateEvent>() { public void handle(WorkerStateEvent event) { try { statusText.setText("Server started - " + tester.getSiteURL(tester.getHostName("<hostname>"), model.getServerPort().intValue(), model.getMusicPath())); } catch (MalformedURLException e) { statusText.setText("Server started - Failed to determine site URL: " + e.getMessage()); } runButton.setText("Stop Server"); disableControls(true); } }); service.setOnSucceeded(new EventHandler<WorkerStateEvent>() { public void handle(WorkerStateEvent event) { statusText.setText("Server stopped"); runButton.setText("Start Server"); disableControls(false); } }); service.setOnFailed(new EventHandler<WorkerStateEvent>() { public void handle(WorkerStateEvent event) { if (service.getException() != null) { service.getException().printStackTrace(); statusText.setText("Server failed: " + service.getException().getMessage()); } else { statusText.setText("Server failed"); } runButton.setText("Start Server"); disableControls(false); } }); runButton.setOnAction(new EventHandler<ActionEvent>() { @Override public void handle(ActionEvent event) { if (service.isRunning()) { service.cancel(); } else { service.reset(); service.start(); } } }); updateAll(); } private BonjourService createBonjour() { try { return new BonjourService(true); } catch (Exception e) { LOGGER.log(Level.WARNING, "Failed to create Bonjour service", e); return null; } } private void startBonjour() { String host = bonjourService.getHostName(); String name = String.format("Test @ %s", tester.getHostName(host)); try { // bonjourService.start(name, model.getServerPort(), tester.getSitePath(model.getMusicPath()), getUser()); // bug in MM 1.5.2 bonjourService.start(name, tester.getSiteURL(host, model.getServerPort().intValue(), model.getMusicPath()), getUser()); LOGGER.info("Bonjour service started."); } catch (Exception e) { LOGGER.log(Level.WARNING, "Failed to start Bonjour service", e); } } private void stopBonjour() { try { bonjourService.stop(); LOGGER.info("Bonjour service stopped."); } catch (Exception e) { LOGGER.log(Level.WARNING, "Failed to stop Bonjour service", e); } } void disableControls(boolean disable) { musicFolderTextField.setDisable(disable); musicFolderChooseButton.setDisable(disable); mountFolderTextField.setDisable(disable); mountFolderChooseButton.setDisable(disable); musicPathChoiceBox.setDisable(disable); musicPathTextField.setDisable(disable); portTextField.setDisable(disable); userTextField.setDisable(disable); passwordField.setDisable(disable); } Pane createView() { GridPane grid = new GridPane(); grid.setAlignment(Pos.CENTER); grid.setHgap(5); grid.setVgap(10); // grid.setGridLinesVisible(true); grid.getColumnConstraints().add(0, ColumnConstraintsBuilder.create().hgrow(Priority.NEVER).build()); grid.getColumnConstraints().add(1, ColumnConstraintsBuilder.create().hgrow(Priority.ALWAYS).build()); grid.getColumnConstraints().add(2, ColumnConstraintsBuilder.create().hgrow(Priority.NEVER).build()); grid.getColumnConstraints().add(3, ColumnConstraintsBuilder.create().hgrow(Priority.ALWAYS).build()); grid.getColumnConstraints().add(4, ColumnConstraintsBuilder.create().hgrow(Priority.NEVER).build()); /* * music folder */ Label musicFolderLabel = new Label("Music Folder"); musicFolderTextField = new TextField(); musicFolderTextField.setPromptText("Input directory, containing your music collection"); musicFolderChooseButton = new Button("..."); grid.add(musicFolderLabel, 0, 1); GridPane.setHalignment(musicFolderLabel, HPos.RIGHT); grid.add(musicFolderTextField, 1, 1, 3, 1); grid.add(musicFolderChooseButton, 4, 1); /* * mount folder */ Label mountFolderLabel = new Label("Site Folder"); mountFolderTextField = new TextField(); mountFolderTextField.setPromptText("Output directory, containing your generated site"); mountFolderChooseButton = new Button("..."); grid.add(mountFolderLabel, 0, 2); GridPane.setHalignment(mountFolderLabel, HPos.RIGHT); grid.add(mountFolderTextField, 1, 2, 3, 1); grid.add(mountFolderChooseButton, 4, 2); /* * music path */ Label musicPathLabel = new Label("Music Path"); musicPathTextField = new TextField(); musicPathTextField.setPromptText("Web path to music, absolute or relative to site URL"); musicPathChoiceBox = new ChoiceBox<String>(FXCollections.observableArrayList("Auto", "Custom")); grid.add(musicPathLabel, 0, 3); GridPane.setHalignment(musicPathLabel, HPos.RIGHT); HBox.setHgrow(musicPathTextField, Priority.ALWAYS); HBox musicPathHBox = new HBox(10); musicPathHBox.getChildren().addAll(musicPathChoiceBox, musicPathTextField); grid.add(musicPathHBox, 1, 3, 3, 1); /* * server port */ Label portLabel = new Label("Server Port"); grid.add(portLabel, 0, 4); GridPane.setHalignment(portLabel, HPos.RIGHT); portTextField = new TextField(); portTextField.setPromptText("Number"); grid.add(portTextField, 1, 4); /* * bonjour */ Label bonjourLabel = new Label("Bonjour"); grid.add(bonjourLabel, 2, 4); GridPane.setHalignment(bonjourLabel, HPos.RIGHT); bonjourCheckBox = new CheckBox("Publish Site"); bonjourCheckBox.setDisable(bonjourService == null); bonjourCheckBox.setTooltip(new Tooltip("Register as local Bonjour service")); grid.add(bonjourCheckBox, 3, 4); /* * user */ Label userLabel = new Label("User"); grid.add(userLabel, 0, 5); GridPane.setHalignment(userLabel, HPos.RIGHT); userTextField = new TextField(); userTextField.setPromptText("Optional"); grid.add(userTextField, 1, 5); /* * password */ Label passwordLabel = new Label("Password"); grid.add(passwordLabel, 2, 5); GridPane.setHalignment(passwordLabel, HPos.RIGHT); passwordField = new PasswordField(); passwordField.setPromptText("Optional"); grid.add(passwordField, 3, 5); /* * run button */ runButton = new Button("Start Server"); runButton.setId("test-button"); runButton.getStyleClass().add("run-button"); HBox runButtonHBox = new HBox(10); runButtonHBox.setAlignment(Pos.BOTTOM_RIGHT); runButtonHBox.getChildren().add(runButton); grid.add(runButtonHBox, 2, 6, 3, 1); GridPane.setVgrow(runButtonHBox, Priority.ALWAYS); BorderPane borderPane = new BorderPane(); borderPane.setCenter(grid); BorderPane.setMargin(grid, new Insets(10)); Text titleText = new Text("Launch MusicMount Test Server"); titleText.setId("test-title"); titleText.getStyleClass().add("tool-title"); borderPane.setTop(titleText); BorderPane.setMargin(titleText, new Insets(15, 10, 0, 10)); statusText = new Text(); statusText.setId("test-status"); statusText.getStyleClass().add("status-text"); borderPane.setBottom(statusText); BorderPane.setMargin(statusText, new Insets(5, 10, 10, 10)); return borderPane; } void updateAll() { updateMusicFolder(); updateMountFolder(); updateMusicPath(); updateRunButton(); updatePort(); updateBonjour(); updateUserAndPassword(); } void updateMusicFolder() { musicFolderTextField.setText(model.getMusicFolder() != null ? model.getMusicFolder().toString() : null); } void updateMountFolder() { mountFolderTextField.setText(model.getMountFolder() != null ? model.getMountFolder().toString() : null); } void updateMusicPath() { musicPathChoiceBox.getSelectionModel().select(model.getCustomMusicPath() == null ? 0 : 1); musicPathTextField.setEditable(model.getCustomMusicPath() != null); musicPathTextField.setText(model.getMusicPath()); if (model.getCustomMusicPath() == null && model.getMusicFolder() != null && model.getMountFolder() != null && !model.getMusicFolder().equals(model.getMountFolder()) && model.getMusicPath() == null) { // no relative path statusText.setText(STATUS_NO_RELATIVE_MUSIC_PATH); } else if (STATUS_NO_RELATIVE_MUSIC_PATH.equals(statusText.getText())) { statusText.setText(null); } } void updateRunButton() { runButton.setDisable(!model.isValidSiteModel() || !tester.checkMusicPath(model.getMusicPath()) || (getUser() == null) != (getPassword() == null)); } void updatePort() { portTextField.setText(model.getServerPort() != null ? model.getServerPort().toString() : null); } void updateBonjour() { bonjourCheckBox.setSelected(model.isBonjour()); } void updateUserAndPassword() { userTextField.setPromptText(getPassword() == null ? "Optional" : "Required"); passwordField.setPromptText(getUser() == null ? "Optional" : "Required"); } String getUser() { return userTextField.getText() == null || userTextField.getText().trim().isEmpty() ? null : userTextField.getText().trim(); } String getPassword() { return passwordField.getText() == null || passwordField.getText().trim().isEmpty() ? null : passwordField.getText().trim(); } public Pane getPane() { return pane; } public Service<Object> getService() { return service; } }