/** * Project-HomeFlix * * Copyright 2016-2019 <@Seil0> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * */ package kellerkinder.HomeFlix.application; import java.io.File; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.io.Writer; import java.math.BigInteger; import java.time.LocalDate; import java.util.Locale; import java.util.ResourceBundle; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.kellerkinder.Alerts.JFX2BtnCancelAlert; import org.kellerkinder.Alerts.JFXInfoAlert; import com.eclipsesource.json.Json; import com.eclipsesource.json.JsonArray; import com.eclipsesource.json.JsonObject; import com.jfoenix.controls.JFXButton; import com.jfoenix.controls.JFXHamburger; import com.jfoenix.transitions.hamburger.HamburgerBackArrowBasicTransition; import javafx.animation.TranslateTransition; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.fxml.FXML; import javafx.scene.control.ScrollPane; import javafx.scene.control.ScrollPane.ScrollBarPolicy; import javafx.scene.effect.BoxBlur; import javafx.scene.input.MouseEvent; import javafx.scene.layout.AnchorPane; import javafx.scene.layout.FlowPane; import javafx.scene.layout.HBox; import javafx.scene.layout.VBox; import javafx.stage.DirectoryChooser; import javafx.stage.FileChooser; import javafx.stage.Stage; import javafx.util.Duration; import kellerkinder.HomeFlix.controller.DBController; import kellerkinder.HomeFlix.controller.OMDbAPIController; import kellerkinder.HomeFlix.controller.UpdateController; import kellerkinder.HomeFlix.controller.XMLController; import kellerkinder.HomeFlix.datatypes.FilmTabelDataType; import kellerkinder.HomeFlix.datatypes.PosterModeElement; public class MainWindowController { // general @FXML private AnchorPane mainAnchorPane; @FXML private HBox topHBox; @FXML private VBox sideMenuVBox; @FXML private JFXHamburger menuHam; @FXML private JFXButton aboutBtn; @FXML private JFXButton settingsBtn; // settings @FXML private SettingsView settingsViewController; // poster-mode @FXML private ScrollPane posterModeScrollPane; @FXML private FlowPane posterModeFlowPane; @FXML private FilmDetailView filmDetailViewController; @FXML private SeriesDetailView seriesDetailViewController; private static MainWindowController instance = null; private DBController dbController; private XMLController xmlController; private Stage primaryStage; private static final Logger LOGGER = LogManager.getLogger(MainWindowController.class.getName()); private boolean menuTrue = false; private String btnStyle; private ObservableList posterEmenents = FXCollections.observableArrayList(); private LocalDate lastValidCache = LocalDate.now().minusDays(30); // current date - 30 days is the last valid cache date public MainWindowController() { // the constructor } public static MainWindowController getInstance() { if (instance == null) { LOGGER.error("There was a fatal error: instance is null!"); instance = new MainWindowController(); } return instance; } public void initialize() { instance = this; xmlController = new XMLController(); dbController = DBController.getInstance(); if (!new File(XMLController.getDirHomeFlix() + "/sources.json").exists()) { XMLController.getDirHomeFlix().mkdir(); LOGGER.warn("sources file not found"); addFirstSource(); xmlController.saveSettings(); } } public void init() { LOGGER.info("Initializing Project-HomeFlix build " + Main.buildNumber); // initialize the GUI and the DBController primaryStage = (Stage) mainAnchorPane.getScene().getWindow(); // set primary stage for dialogs posterModeScrollPane.setVbarPolicy(ScrollBarPolicy.ALWAYS); setLocalUI(); applyColor(); // TODO only on first start initActions(); dbController.init(); // load data list in gui posterModeStartup(); checkAutoUpdate(); // TODO async } // Initializing the actions private void initActions() { // general actions HamburgerBackArrowBasicTransition burgerTask = new HamburgerBackArrowBasicTransition(menuHam); menuHam.addEventHandler(MouseEvent.MOUSE_PRESSED, (e) -> { if (menuTrue) { sideMenuSlideOut(); burgerTask.setRate(-1.0); burgerTask.play(); menuTrue = false; } else { sideMenuSlideIn(); burgerTask.setRate(1.0); burgerTask.play(); menuTrue = true; } if (settingsViewController.isVisible()) { settingsViewController.setVisible(false); } }); } // general fxml actions @FXML private void aboutBtnAction() { String bodyText = "Project HomeFlix \nVersion: " + Main.version + " (Build: " + Main.buildNumber + ") \"" + Main.versionName + "\" \n" + XMLController.getLocalBundle().getString("infoText"); JFXInfoAlert infoAlert = new JFXInfoAlert("Project HomeFlix", bodyText, btnStyle, primaryStage); infoAlert.showAndWait(); } @FXML private void settingsBtnclicked() { settingsViewController.setVisible(!settingsViewController.isVisible()); } /** * we need to get the path for the first source from the user and add it to * sources.json, if the user ends the file-/directory-chooser the program will exit */ private void addFirstSource() { JFX2BtnCancelAlert selectFirstSource = new JFX2BtnCancelAlert( XMLController.getLocalBundle().getString("addSourceHeader"), XMLController.getLocalBundle().getString("addSourceBody"), "-fx-button-type: RAISED; -fx-background-color: #ee3523; -fx-text-fill: BLACK;", XMLController.getLocalBundle().getString("addDirectory"), XMLController.getLocalBundle().getString("addStreamSource"), XMLController.getLocalBundle().getString("cancelBtnText"), primaryStage); // directory action selectFirstSource.setBtn1Action(e -> { DirectoryChooser directoryChooser = new DirectoryChooser(); directoryChooser.setTitle(XMLController.getLocalBundle().getString("addDirectory")); File selectedFolder = directoryChooser.showDialog(primaryStage); if (selectedFolder != null && selectedFolder.exists()) { selectFirstSource.getAlert().close(); writeSource(selectedFolder.getPath(), "local"); settingsViewController.loadInitSources(); } else { LOGGER.error("The selected folder dosen't exist!"); System.exit(1); } }); // streaming action selectFirstSource.setBtn2Action(e -> { FileChooser fileChooser = new FileChooser(); fileChooser.setTitle(XMLController.getLocalBundle().getString("addStreamSource")); File selectedFile = fileChooser.showOpenDialog(primaryStage); if (selectedFile != null && selectedFile.exists()) { selectFirstSource.getAlert().close(); writeSource(selectedFile.getPath(), "stream"); settingsViewController.loadInitSources(); } else { LOGGER.error("The selected file dosen't exist!"); System.exit(1); } }); selectFirstSource.showAndWait(); } /** * add a source to the sources file * * @param path to the source * @param mode of the source (local or streaming) */ void writeSource(String path, String mode) { JsonArray newsources = null; try { // read old array File oldSources = new File(XMLController.getDirHomeFlix() + "/sources.json"); newsources = oldSources.exists() ? Json.parse(new FileReader(XMLController.getDirHomeFlix() + "/sources.json")).asArray() : Json.array(); // add new source JsonObject source = Json.object().add("path", path).add("mode", mode); newsources.add(source); Writer writer = new FileWriter(XMLController.getDirHomeFlix() + "/sources.json"); newsources.writeTo(writer); writer.close(); } catch (IOException e) { LOGGER.error("Error while writing sources file!", e); } } /** * set the color of the GUI-Elements if usedColor is less than checkColor set * text fill white, else black */ void applyColor() { String menuBtnStyle; BigInteger usedColor = new BigInteger(XMLController.getColor(), 16); BigInteger checkColor = new BigInteger("78909cff", 16); menuHam.getStyleClass().clear(); if (usedColor.compareTo(checkColor) == -1) { btnStyle = "-fx-button-type: RAISED; -fx-background-color: #" + XMLController.getColor() + "; -fx-text-fill: WHITE;"; menuBtnStyle = "-fx-text-fill: WHITE;"; menuHam.getStyleClass().add("jfx-hamburgerW"); } else { btnStyle = "-fx-button-type: RAISED; -fx-background-color: #" + XMLController.getColor() + "; -fx-text-fill: BLACK;"; menuBtnStyle = "-fx-text-fill: BLACK;"; menuHam.getStyleClass().add("jfx-hamburgerB"); } // boxes and TextFields sideMenuVBox.setStyle("-fx-background-color: #" + XMLController.getColor() + ";"); topHBox.setStyle("-fx-background-color: #" + XMLController.getColor() + ";"); // menu buttons settingsBtn.setStyle(menuBtnStyle); aboutBtn.setStyle(menuBtnStyle); settingsViewController.updateColor(btnStyle); } // slide in in 400ms private void sideMenuSlideIn() { TranslateTransition translateTransition = new TranslateTransition(Duration.millis(400), sideMenuVBox); translateTransition.setFromX(0); translateTransition.setToX(150); translateTransition.play(); } // slide out in 400ms private void sideMenuSlideOut() { TranslateTransition translateTransition = new TranslateTransition(Duration.millis(400), sideMenuVBox); translateTransition.setFromX(150); translateTransition.setToX(0); translateTransition.play(); } /** * set the local based on the languageChoisBox selection */ void setLocalUI() { // TODO switch expressions switch (XMLController.getUsrLocal()) { case "en_US": XMLController.setLocalBundle(ResourceBundle.getBundle("locals.HomeFlix-Local", Locale.US)); // us_English break; case "de_DE": XMLController.setLocalBundle(ResourceBundle.getBundle("locals.HomeFlix-Local", Locale.GERMAN)); // German break; default: XMLController.setLocalBundle(ResourceBundle.getBundle("locals.HomeFlix-Local", Locale.US)); // default local break; } settingsViewController.updateGUILocal(); filmDetailViewController.updateGUILocal(); seriesDetailViewController.updateGUILocal(); aboutBtn.setText(XMLController.getLocalBundle().getString("info")); settingsBtn.setText(XMLController.getLocalBundle().getString("settings")); } // if AutoUpdate, then check for updates private void checkAutoUpdate() { LOGGER.info("AutoUpdate: looking for updates on startup ..."); if (XMLController.isAutoUpdate() && UpdateController.isUpdateAvailable()) { UpdateController.update(); } } /** * Poser Mode */ private void posterModeStartup() { checkAllPosters(); addAllPosters(); checkCache(); } /** * check if all posters are cached, if not cache the missing ones */ void checkAllPosters() { dbController.refreshDataBase(); // refreshes the database after a source path was added // get all not cached entries, none of them should have a cached poster ExecutorService executor = Executors.newFixedThreadPool(5); for (FilmTabelDataType entry : dbController.getAllNotCachedEntries()) { // System.out.println(entry.getStreamUrl() + " is NOT cached!"); Runnable OMDbAPIWorker = new OMDbAPIController(entry); executor.execute(OMDbAPIWorker); } executor.shutdown(); // TODO show loading screen // we might need this as otherwise it would load before all tasks are finished try { executor.awaitTermination(1, TimeUnit.MINUTES); } catch (InterruptedException e) { LOGGER.error(e); } System.out.println("finished refresh"); } /** * add all cached films/series to the PosterMode GUI */ void addAllPosters() { // refresh the posterModeElements list posterEmenents.clear(); posterEmenents = dbController.getPosterElementsList(); // returns a list of all PosterElements stored in the database // add button onAction for (PosterModeElement element : posterEmenents) { element.getButton().addEventHandler(MouseEvent.MOUSE_CLICKED, (event) -> { enableBlur(); // blur the FlowPane // if the selected element is a file it's a film, else a series if (new File(element.getStreamURL()).isFile() || element.getStreamURL().contains("http")) { filmDetailViewController.setFilm(element.getStreamURL()); filmDetailViewController.showPane(); } else { seriesDetailViewController.setSeries(element.getStreamURL()); seriesDetailViewController.showPane(); } }); } posterModeFlowPane.getChildren().clear(); // remove all GUIElements from the posterModeFlowPane posterModeFlowPane.getChildren().addAll(posterEmenents); // add all films/series as new GUIElements to the posterModeFlowPane System.out.println("added gui elements"); } // TODO can this be done in dbController? with dbController.refreshDataBase(); /** * check if the cache is to old, if so update asynchron */ private void checkCache() { ExecutorService executor = Executors.newFixedThreadPool(5); for(FilmTabelDataType entry : dbController.getStreamsList()) { if (dbController.getCacheDate(entry.getStreamUrl()).isBefore(lastValidCache)) { // System.out.println(entry.getTitle() + " chached on: " + dbController.getCacheDate(entry.getStreamUrl())); Runnable OMDbAPIWorker = new OMDbAPIController(entry); executor.execute(OMDbAPIWorker); } } executor.shutdown(); } private void enableBlur() { BoxBlur boxBlur = new BoxBlur(); boxBlur.setWidth(9); boxBlur.setHeight(7); boxBlur.setIterations(3); posterModeFlowPane.setEffect(boxBlur); } public void disableBlur() { posterModeFlowPane.setEffect(null); } }