diff --git a/.classpath b/.classpath deleted file mode 100644 index d9ff1e3..0000000 --- a/.classpath +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/.gitignore b/.gitignore index f8db64b..43baad2 100644 --- a/.gitignore +++ b/.gitignore @@ -49,4 +49,6 @@ config.xml .directory target/ apiKeys.json - +.classpath +.project +.settings/* diff --git a/.project b/.project deleted file mode 100644 index 01f0bec..0000000 --- a/.project +++ /dev/null @@ -1,23 +0,0 @@ - - - Project-HomeFlix - - - - - - org.eclipse.jdt.core.javabuilder - - - - - org.eclipse.m2e.core.maven2Builder - - - - - - org.eclipse.jdt.core.javanature - org.eclipse.m2e.core.maven2Nature - - diff --git a/.settings/org.eclipse.core.resources.prefs b/.settings/org.eclipse.core.resources.prefs deleted file mode 100644 index 839d647..0000000 --- a/.settings/org.eclipse.core.resources.prefs +++ /dev/null @@ -1,5 +0,0 @@ -eclipse.preferences.version=1 -encoding//src/main/java=UTF-8 -encoding//src/main/resources=UTF-8 -encoding//src/test/java=UTF-8 -encoding/=UTF-8 diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs deleted file mode 100644 index 6f1d295..0000000 --- a/.settings/org.eclipse.jdt.core.prefs +++ /dev/null @@ -1,13 +0,0 @@ -eclipse.preferences.version=1 -org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled -org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate -org.eclipse.jdt.core.compiler.codegen.targetPlatform=9 -org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve -org.eclipse.jdt.core.compiler.compliance=9 -org.eclipse.jdt.core.compiler.debug.lineNumber=generate -org.eclipse.jdt.core.compiler.debug.localVariable=generate -org.eclipse.jdt.core.compiler.debug.sourceFile=generate -org.eclipse.jdt.core.compiler.problem.assertIdentifier=error -org.eclipse.jdt.core.compiler.problem.enumIdentifier=error -org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning -org.eclipse.jdt.core.compiler.source=9 diff --git a/.settings/org.eclipse.m2e.core.prefs b/.settings/org.eclipse.m2e.core.prefs deleted file mode 100644 index f897a7f..0000000 --- a/.settings/org.eclipse.m2e.core.prefs +++ /dev/null @@ -1,4 +0,0 @@ -activeProfiles= -eclipse.preferences.version=1 -resolveWorkspaceProjects=true -version=1 diff --git a/README.md b/README.md index 2491954..fce24c2 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,18 @@ -![Total Downloads](https://img.shields.io/github/downloads/Seil0/Project-HomeFlix/total.svg?style=flat-square) -[![Latest](https://img.shields.io/github/release/Seil0/Project-HomeFlix/all.svg?style=flat-square)](https://github.com/Seil0/Project-HomeFlix/releases) +[![Latest](https://img.shields.io/badge/Download-latest-brightgreen.svg?style=flat-square)](https://git.mosad.xyz/Seil0/Project-HomeFlix/releases) [![License: GPL v3](https://img.shields.io/badge/License-GPL%20v3-blue.svg?style=flat-square)](https://www.gnu.org/licenses/gpl-3.0) # Project-HomeFlix Project-HomeFlix is a Kellerkinder Project, that alowes you to sort all your local saved movies in clean UI. Project-HomeFlix is free and open-source software and uses other open-source projects to provied a nice user experience. ## Installation -Simply download the Project-HomeFlix.jar from [releases](https://github.com/Seil0/Project-HomeFlix/releases), make sure you have the latest version of java 8 oracle jre/jdk installed, open the .jar file. If you need additional information pleas visit our [wiki](https://github.com/Seil0/Project-HomeFlix/wiki). +Simply download the Project-HomeFlix.jar from [releases](https://git.mosad.xyz/Seil0/Project-HomeFlix/releases), make sure you have the latest version of java 8 oracle jre/jdk installed, open the .jar file. If you need additional information pleas visit our [wiki](https://git.mosad.xyz/Seil0/Project-HomeFlix/wiki). ## Development information -The dev branch is **only merged** into master when a **new release** is released, so **master contains the latest released version**. Please commit all changes to [dev](https://github.com/Seil0/Project-HomeFlix/tree/dev). +The dev branch is **only merged** into master when a **new release** is released, so **master contains the latest released version**. Please commit all changes to [dev](https://git.mosad.xyz/Seil0/Project-HomeFlix/src/branch/dev). -Librarys used in this Project: -JFoenix: https://github.com/jfoenixadmin/JFoenix -minimal-json: https://github.com/ralfstx/minimal-json -sqlite-jdbc: https://github.com/xerial/sqlite-jdbc -apache commons io : https://commons.apache.org/proper/commons-io/ +[Libraries used in this Project](https://git.mosad.xyz/Seil0/Project-HomeFlix/wiki/Documentation#used-libraries-and-apis) -## screenshots -![Screenshot](https://github.com/Seil0/Seil0.github.io/blob/master/images/Project-HomeFlix_MainWindow.png) +## Screenshots +![Screenshot](https://raw.githubusercontent.com/Seil0/Seil0.github.io/master/images/Project-HomeFlix_MainWindow.png) -Project-HomeFlix © 2016-2018 Kellerkinder ([Seil0](https://github.com/Seil0), [Windoofs](https://github.com/Windoofs)) -www.kellerkinder.xyz +Project-HomeFlix © 2016-2019 mosad www.mosad.xyz, Project by [@Seil0](https://git.mosad.xyz/Seil0) and [@localhorst](https://git.mosad.xyz/localhorst) diff --git a/pom.xml b/pom.xml index 95f22a2..f69889e 100644 --- a/pom.xml +++ b/pom.xml @@ -1,103 +1,141 @@ - - 4.0.0 + + 4.0.0 - org.kellerkinder - Project-HomeFlix - 0.7.0 - jar + org.kellerkinder + Project-HomeFlix + 0.7.0 + jar - Project-HomeFlix - http://www.kellerkinder.xyz + Project-HomeFlix + http://www.mosad.xyz - - UTF-8 - + + UTF-8 + - - - - junit - junit - 4.12 - test - - - - commons-io - commons-io - 2.6 - + - - com.jfoenix - jfoenix - 9.0.3 - - - - com.eclipsesource.minimal-json - minimal-json - 0.9.5 - - - - org.xerial - sqlite-jdbc - 3.21.0.1 - - - - org.apache.logging.log4j - log4j-api - 2.11.0 - + + junit + junit + 4.12 + test + - - org.apache.logging.log4j - log4j-core - 2.11.0 - - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.7.0 - - 9 - 9 - true - true - - + + org.openjfx + javafx-controls + 12.0.1 + + + + org.openjfx + javafx-fxml + 12.0.1 + + + + org.openjfx + javafx-media + 12.0.1 + + + + commons-io + commons-io + 2.6 + + + + com.jfoenix + jfoenix + 9.0.9 + + + + com.eclipsesource.minimal-json + minimal-json + 0.9.5 + + + + org.xerial + sqlite-jdbc + 3.27.2.1 + + + + org.apache.logging.log4j + log4j-api + 2.11.2 + + + + org.apache.logging.log4j + log4j-core + 2.11.2 + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.0 + + 11 + 11 + + true + true + + + + + org.codehaus.mojo + exec-maven-plugin + 1.6.0 + + + + java + + + + + kellerkinder.HomeFlix.application.Main + + + + + org.apache.maven.plugins + maven-shade-plugin + 3.2.1 + + Project-HomeFlix + true + + + kellerkinder.HomeFlix.application.JavaFX11Main + + + + + + package + + shade + + + + + + + - - org.apache.maven.plugins - maven-shade-plugin - 3.1.1 - - true - - - kellerkinder.HomeFlix.application.Main - - - - - - package - - shade - - - - - - - - diff --git a/src/main/java/kellerkinder/HomeFlix/application/FilmDetailView.java b/src/main/java/kellerkinder/HomeFlix/application/FilmDetailView.java new file mode 100644 index 0000000..8c02475 --- /dev/null +++ b/src/main/java/kellerkinder/HomeFlix/application/FilmDetailView.java @@ -0,0 +1,226 @@ +/** + * 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.awt.Desktop; +import java.io.File; +import java.io.IOException; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import com.jfoenix.controls.JFXButton; + +import javafx.animation.FadeTransition; +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.AnchorPane; +import javafx.scene.text.Text; +import javafx.util.Duration; +import kellerkinder.HomeFlix.controller.DBController; +import kellerkinder.HomeFlix.controller.XMLController; +import kellerkinder.HomeFlix.player.Player; + +public class FilmDetailView { + + @FXML private AnchorPane filmDVPane; + + @FXML private Label lblTitle; + @FXML private Label lblYear; + @FXML private Label lblScore; + + @FXML private Label lblCrew; + @FXML private Label lblDirectors; + @FXML private Label lblDirectorsInfo; + @FXML private Label lblWriters; + @FXML private Label lblWritersInfo; + @FXML private Label lblActors; + @FXML private Label lblActorsInfo; + + @FXML private Label lblInfo; + @FXML private Label lblRuntimeInfo; + @FXML private Label lblRuntime; + @FXML private Label lblLanguageInfo; + @FXML private Label lblLanguage; + @FXML private Label lblRevenueInfo; + @FXML private Label lblRevenue; + @FXML private Label lblRatingInfo; + @FXML private Label lblRating; + + @FXML private JFXButton btnWishlist; + @FXML private JFXButton btnFavourite; + @FXML private JFXButton btnHide; + @FXML private JFXButton btnPlay; + @FXML private JFXButton btnDirectory; + + @FXML private ImageView wishlistIcon; + @FXML private ImageView favoriteIcon; + @FXML private ImageView imgPoster; + + @FXML private Text textPlot; + + private DBController dbController; + private static final Logger LOGGER = LogManager.getLogger(FilmDetailView.class.getName()); + private String currentStreamURL; + + public void initialize() { + dbController = DBController.getInstance(); + filmDVPane.setStyle("-fx-background-color: rgba(89,89,89,0.9);"); + } + + @FXML + private void btnWishlistAction() { + + } + + @FXML + private void btnFavouriteAction() { + dbController.toggleFavoriteState(currentStreamURL); + + // update the favorite icon + if(dbController.getFavoriteState(currentStreamURL) == 1) { + favoriteIcon.setImage(new Image("icons/baseline_favorite_black_48dp.png")); + } else { + favoriteIcon.setImage(new Image("icons/baseline_favorite_border_black_48dp.png")); + } + } + + @FXML + private void btnHideAction() { + hidePane(); + } + + @FXML + private void btnPlayAction() { + playFilm(); + } + + @FXML + private void btnDirectoryAction() { + File dest = new File(currentStreamURL).getParentFile(); + + if (!System.getProperty("os.name").contains("Linux")) { + try { + Desktop.getDesktop().open(dest); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + /** + * set the cached data of a stream to the FilmDetailView + * @param streamURL URL of the stream + */ + public void setFilm(String streamURL) { + currentStreamURL = streamURL; + String[] cacheInfo = dbController.readCache(streamURL); // get the cache data from the database + + // add the cache data to the GUI + lblTitle.setText(cacheInfo[0]); + lblYear.setText("(" + cacheInfo[1] + ")"); + lblScore.setText(XMLController.getLocalBundle().getString("score") + ": " + cacheInfo[15] + "%"); + + textPlot.setText(cacheInfo[11]); + + lblDirectors.setText(cacheInfo[8]); + lblWriters.setText(cacheInfo[9]); + lblActors.setText(cacheInfo[10]); + + lblRuntime.setText(cacheInfo[6]); + lblLanguage.setText(cacheInfo[12]); + lblRevenue.setText(cacheInfo[18]); + lblRating.setText(cacheInfo[2]); + + try { + if (new File(cacheInfo[20]).isFile()) { + imgPoster.setImage(new Image(new File(cacheInfo[20]).toURI().toString())); + } else { + imgPoster.setImage(new Image(cacheInfo[20])); + } + } catch (Exception e) { + imgPoster.setImage(new Image("icons/Homeflix_Poster.png")); + LOGGER.error("No Poster found, useing default."); + } + + // set the favorite correct icon + if(dbController.getFavoriteState(streamURL) == 1) { + favoriteIcon.setImage(new Image("icons/baseline_favorite_black_48dp.png")); + } else { + favoriteIcon.setImage(new Image("icons/baseline_favorite_border_black_48dp.png")); + } + + } + + /** + * update the text of all static GUI elements of FilmDeatilView + */ + public void updateGUILocal() { + lblCrew.setText(XMLController.getLocalBundle().getString("crew")); + lblDirectorsInfo.setText(XMLController.getLocalBundle().getString("directors")); + lblWritersInfo.setText(XMLController.getLocalBundle().getString("writers")); + lblActorsInfo.setText(XMLController.getLocalBundle().getString("actors")); + + lblInfo.setText(XMLController.getLocalBundle().getString("info")); + lblRuntimeInfo.setText(XMLController.getLocalBundle().getString("runtime")); + lblLanguageInfo.setText(XMLController.getLocalBundle().getString("language")); + lblRevenueInfo.setText(XMLController.getLocalBundle().getString("boxOffice")); + lblRatingInfo.setText(XMLController.getLocalBundle().getString("rated")); + } + + /** + * show the FilmDVpane + */ + public void showPane() { + filmDVPane.setVisible(true); + FadeTransition fadeIn = new FadeTransition(Duration.millis(300), filmDVPane); + fadeIn.setFromValue(0.3); + fadeIn.setToValue(1.0); + fadeIn.play(); + } + + /** + * hide the FilmDVpane + */ + private void hidePane() { + FadeTransition fadeOut = new FadeTransition(Duration.millis(200), filmDVPane); + fadeOut.setFromValue(1.0); + fadeOut.setToValue(0.3); + fadeOut.play(); + + filmDVPane.setVisible(false); + + MainWindowController.getInstance().disableBlur(); // disable blur + } + + private void playFilm() { + if(new File(currentStreamURL).isDirectory()) { + return; + } + + new Player(currentStreamURL); + } + +} diff --git a/src/main/java/kellerkinder/HomeFlix/application/JavaFX11Main.java b/src/main/java/kellerkinder/HomeFlix/application/JavaFX11Main.java new file mode 100644 index 0000000..1b2bc01 --- /dev/null +++ b/src/main/java/kellerkinder/HomeFlix/application/JavaFX11Main.java @@ -0,0 +1,8 @@ +package kellerkinder.HomeFlix.application; + +public class JavaFX11Main { + + public static void main(String[] args) { + Main.main(args); + } +} diff --git a/src/main/java/kellerkinder/HomeFlix/application/Main.java b/src/main/java/kellerkinder/HomeFlix/application/Main.java index d9012c4..da229a7 100644 --- a/src/main/java/kellerkinder/HomeFlix/application/Main.java +++ b/src/main/java/kellerkinder/HomeFlix/application/Main.java @@ -1,7 +1,7 @@ /** * Project-HomeFlix * - * Copyright 2016-2018 <@Seil0> + * 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 @@ -19,222 +19,98 @@ * MA 02110-1301, USA. * */ + package kellerkinder.HomeFlix.application; import java.io.File; import java.io.IOException; -import java.util.Locale; -import java.util.ResourceBundle; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.kellerkinder.Alerts.JFX2BtnCancelAlert; import javafx.application.Application; -import javafx.event.ActionEvent; -import javafx.event.EventHandler; import javafx.fxml.FXMLLoader; import javafx.scene.Scene; import javafx.scene.image.Image; import javafx.scene.layout.AnchorPane; -import javafx.stage.DirectoryChooser; -import javafx.stage.FileChooser; import javafx.stage.Stage; -import javafx.stage.WindowEvent; +import kellerkinder.HomeFlix.controller.XMLController; public class Main extends Application { - private Stage primaryStage; private Scene scene; private AnchorPane pane; private MainWindowController mainWindowController; - private static String userHome = System.getProperty("user.home"); - private static String userName = System.getProperty("user.name"); - private static String osName = System.getProperty("os.name"); - private static String osArch = System.getProperty("os.arch"); - private static String osVers = System.getProperty("os.version"); - private static String javaVers = System.getProperty("java.version"); - private static String javaVend = System.getProperty("java.vendor"); - private static String local = System.getProperty("user.language") + "_" + System.getProperty("user.country"); - private String dirWin = userHome + "/Documents/HomeFlix"; // Windows: C:/Users/"User"/Documents/HomeFlix - private String dirLinux = userHome + "/HomeFlix"; // Linux: /home/"User"/HomeFlix - private File directory; - private File configFile; - private File posterCache; - private ResourceBundle bundle; + private static XMLController xmlController; private static Logger LOGGER; + public static final String version = "0.8.0"; + public static final String buildNumber = "173"; + public static final String versionName = "toothless dragon"; @Override public void start(Stage primaryStage) throws IOException { - LOGGER.info("OS: " + osName + " " + osVers + " " + osArch); - LOGGER.info("Java: " + javaVend + " " + javaVers); - LOGGER.info("User: " + userName + " " + userHome); - - this.primaryStage = primaryStage; - mainWindow(); - } - - /** - * initialize the mainWindowController, GUI and load the saved settings or call addFirstSource - * initialize the primaryStage and set the file/directory paths - */ - private void mainWindow(){ + + //initialize the mainWindowController and the primaryStage try { FXMLLoader loader = new FXMLLoader(); - loader.setLocation(ClassLoader.getSystemResource("fxml/MainWindow.fxml")); + loader.setLocation(getClass().getResource("/fxml/MainWindow.fxml")); pane = (AnchorPane) loader.load(); - primaryStage.setMinHeight(600.00); - primaryStage.setMinWidth(1000.00); - primaryStage.setResizable(false); + primaryStage.setMinHeight(600.00 + 34); // 34 -> window decoration + primaryStage.setMinWidth(1130.00); + //primaryStage.setResizable(false); primaryStage.setTitle("Project HomeFlix"); primaryStage.getIcons().add(new Image(Main.class.getResourceAsStream("/icons/Homeflix_Icon_64x64.png"))); //adds application icon - primaryStage.setOnCloseRequest(new EventHandler() { - public void handle(WindowEvent we) { - System.exit(1); - } - }); - - mainWindowController = loader.getController(); //Link of FXMLController and controller class - mainWindowController.setMain(this); //call setMain + primaryStage.setOnCloseRequest(event -> System.exit(0)); - - // get OS and the specific paths - if (osName.contains("Windows")) { - directory = new File(dirWin); - configFile = new File(dirWin + "/config.xml"); - posterCache = new File(dirWin + "/posterCache"); - } else { - directory = new File(dirLinux); - configFile = new File(dirLinux + "/config.xml"); - posterCache = new File(dirLinux + "/posterCache"); - } - // generate window scene = new Scene(pane); // create new scene, append pane to scene scene.getStylesheets().add(getClass().getResource("/css/MainWindow.css").toExternalForm()); primaryStage.setScene(scene); // append scene to stage primaryStage.show(); // show stage - // startup checks - if (!configFile.exists()) { - directory.mkdir(); - addFirstSource(); - mainWindowController.setColor("ee3523"); - mainWindowController.setFontSize(17.0); - mainWindowController.setAutoUpdate(false); - mainWindowController.setLocal(local); - mainWindowController.saveSettings(); - } - - if (!posterCache.exists()) { - posterCache.mkdir(); - } - - // init here as it loads the games to the mwc and the gui, therefore the window must exist + mainWindowController = loader.getController(); //Link of FXMLController and controller class mainWindowController.init(); - mainWindowController.getDbController().init(); } catch (IOException e) { - LOGGER.error(e); + LOGGER.error("Error while loading in Main", e); } } /** - * 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() { - switch (local) { - case "en_US": - bundle = ResourceBundle.getBundle("locals.HomeFlix-Local", Locale.US); // us_english - break; - case "de_DE": - bundle = ResourceBundle.getBundle("locals.HomeFlix-Local", Locale.GERMAN); // German - break; - default: - bundle = ResourceBundle.getBundle("locals.HomeFlix-Local", Locale.US); // default local - break; - } - - JFX2BtnCancelAlert selectFirstSource = new JFX2BtnCancelAlert(bundle.getString("addSourceHeader"), - bundle.getString("addSourceBody"), - "-fx-button-type: RAISED; -fx-background-color: #ee3523; -fx-text-fill: BLACK;", - bundle.getString("addDirectory"), bundle.getString("addStreamSource"), - bundle.getString("cancelBtnText"), primaryStage); - - // directory action - EventHandler btn1Action = new EventHandler() { - @Override - public void handle(ActionEvent event) { - DirectoryChooser directoryChooser = new DirectoryChooser(); - directoryChooser.setTitle(bundle.getString("addDirectory")); - File selectedFolder = directoryChooser.showDialog(primaryStage); - if (selectedFolder != null && selectedFolder.exists()) { - mainWindowController.addSource(selectedFolder.getPath(), "local"); - selectFirstSource.getAlert().close(); - } else { - LOGGER.error("The selected folder dosen't exist!"); - System.exit(1); - } - } - }; - - // streaming action - EventHandler btn2Action = new EventHandler() { - @Override - public void handle(ActionEvent event) { - FileChooser fileChooser = new FileChooser(); - fileChooser.setTitle("addStreamSource"); - File selectedFile = fileChooser.showOpenDialog(getPrimaryStage()); - if (selectedFile != null && selectedFile.exists()) { - mainWindowController.addSource(selectedFile.getPath(), "stream"); - selectFirstSource.getAlert().close(); - } else { - LOGGER.error("The selected file dosen't exist!"); - System.exit(1); - } - } - }; - selectFirstSource.setBtn1Action(btn1Action); - selectFirstSource.setBtn2Action(btn2Action); - selectFirstSource.showAndWait(); - } - - /** - * set the log file location and initialize the logger - * launch the GUI + * set the log file location and initialize the logger launch the GUI * @param args arguments given at the start */ public static void main(String[] args) { - if (System.getProperty("os.name").equals("Windows")) { - System.setProperty("logFilename", userHome + "/Documents/HomeFlix/app.log"); - File logFile = new File(userHome + "/Documents/HomeFlix/app.log"); - logFile.delete(); + // Logger initialization + String logPath = ""; + + if (System.getProperty("os.name").contains("Windows")) { + logPath = System.getProperty("user.home") + "/Documents/HomeFlix/app.log"; } else { - System.setProperty("logFilename", userHome + "/HomeFlix/app.log"); - File logFile = new File(userHome + "/HomeFlix/app.log"); - logFile.delete(); + logPath = System.getProperty("user.home") + "/HomeFlix/app.log"; } + + System.setProperty("logFilename", logPath); + File logFile = new File(logPath); + logFile.delete(); LOGGER = LogManager.getLogger(Main.class.getName()); + + LOGGER.info("OS: " + XMLController.getOsName() + " " + XMLController.getOsVers() + " " + XMLController.getOsVers()); + LOGGER.info("Java: " + XMLController.getJavaVend() + " " + XMLController.getJavaVers()); + LOGGER.info("User: " + XMLController.getUserName() + " " + XMLController.getUserHome()); + + xmlController = new XMLController(); + + if (!XMLController.getConfigFile().exists()) { + xmlController.saveSettings(); // save the settings file with default values if it doesn't exist + } + + xmlController.loadSettings(); + + if (!XMLController.getPosterCache().exists()) { + XMLController.getPosterCache().mkdir(); + } + launch(args); } - public Stage getPrimaryStage() { - return primaryStage; - } - - public AnchorPane getPane() { - return pane; - } - - public File getDirectory() { - return directory; - } - - public File getConfigFile() { - return configFile; - } - - public File getPosterCache() { - return posterCache; - } -} \ No newline at end of file +} diff --git a/src/main/java/kellerkinder/HomeFlix/application/MainWindowController.java b/src/main/java/kellerkinder/HomeFlix/application/MainWindowController.java index 45cb8b0..65e6090 100644 --- a/src/main/java/kellerkinder/HomeFlix/application/MainWindowController.java +++ b/src/main/java/kellerkinder/HomeFlix/application/MainWindowController.java @@ -1,7 +1,7 @@ /** * Project-HomeFlix * - * Copyright 2016-2018 <@Seil0> + * 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 @@ -19,1062 +19,427 @@ * MA 02110-1301, USA. * */ + package kellerkinder.HomeFlix.application; -import java.awt.Desktop; -import java.io.BufferedReader; import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; import java.io.Writer; import java.math.BigInteger; -import java.net.URLConnection; +import java.time.LocalDate; import java.util.Locale; -import java.util.Properties; 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.JFXColorPicker; import com.jfoenix.controls.JFXHamburger; -import com.jfoenix.controls.JFXSlider; -import com.jfoenix.controls.JFXTextField; -import com.jfoenix.controls.JFXToggleButton; import com.jfoenix.transitions.hamburger.HamburgerBackArrowBasicTransition; import javafx.animation.TranslateTransition; -import javafx.beans.value.ChangeListener; -import javafx.beans.value.ObservableValue; import javafx.collections.FXCollections; import javafx.collections.ObservableList; -import javafx.event.ActionEvent; -import javafx.event.EventHandler; import javafx.fxml.FXML; -import javafx.scene.control.ChoiceBox; -import javafx.scene.control.ContextMenu; -import javafx.scene.control.Label; -import javafx.scene.control.MenuItem; import javafx.scene.control.ScrollPane; -import javafx.scene.control.TableColumn; -import javafx.scene.control.TableView; -import javafx.scene.control.TreeItem; -import javafx.scene.control.TreeTableColumn; -import javafx.scene.control.TreeTableColumn.SortType; -import javafx.scene.control.TreeTableView; -import javafx.scene.image.Image; -import javafx.scene.image.ImageView; +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.scene.paint.Color; -import javafx.scene.text.Font; -import javafx.scene.text.TextFlow; 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.datatypes.SourceDataType; -import kellerkinder.HomeFlix.player.Player; +import kellerkinder.HomeFlix.controller.XMLController; import kellerkinder.HomeFlix.datatypes.FilmTabelDataType; +import kellerkinder.HomeFlix.datatypes.PosterModeElement; public class MainWindowController { - @FXML - private AnchorPane mainAnchorPane; - @FXML - private AnchorPane tableModeAnchorPane; + // general + @FXML private AnchorPane mainAnchorPane; - @FXML - private ScrollPane settingsScrollPane; - @FXML - private ScrollPane textScrollPane; + @FXML private HBox topHBox; + @FXML private VBox sideMenuVBox; - @FXML - private HBox topHBox; + @FXML private JFXHamburger menuHam; - @FXML - private VBox sideMenuVBox; + @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; - @FXML - private TreeTableView filmsTreeTable; - - @FXML - private TableView sourcesTable; - - @FXML - private TextFlow textFlow; - - @FXML - private JFXButton playbtn; - @FXML - private JFXButton openfolderbtn; - @FXML - private JFXButton returnBtn; - @FXML - private JFXButton forwardBtn; - @FXML - private JFXButton aboutBtn; - @FXML - private JFXButton settingsBtn; - @FXML - private JFXButton updateBtn; - @FXML - private JFXButton addDirectoryBtn; - @FXML - private JFXButton addStreamSourceBtn; - - @FXML - private JFXHamburger menuHam; - - @FXML - private JFXToggleButton autoUpdateToggleBtn; - @FXML - private JFXToggleButton autoplayToggleBtn; - - @FXML - private JFXTextField searchTextField; - - @FXML - private JFXColorPicker colorPicker; - - @FXML - private ChoiceBox languageChoisBox = new ChoiceBox<>(); - @FXML - private ChoiceBox branchChoisBox = new ChoiceBox<>(); - - @FXML - private JFXSlider fontsizeSlider; - - @FXML - private Label homeflixSettingsLbl; - @FXML - private Label mainColorLbl; - @FXML - private Label fontsizeLbl; - @FXML - private Label languageLbl; - @FXML - private Label updateLbl; - @FXML - private Label branchLbl; - @FXML - private Label sourcesLbl; - @FXML - private Label versionLbl; - - @FXML - private ImageView posterImageView; - private ImageView imv1; - - @FXML - private TreeItem filmRoot = new TreeItem<>(new FilmTabelDataType("", "", "", "", false, false, imv1)); - @FXML - private TreeTableColumn columnStreamUrl = new TreeTableColumn<>("File Name"); - @FXML - private TreeTableColumn columnTitle = new TreeTableColumn<>("Title"); - @FXML - private TreeTableColumn columnSeason = new TreeTableColumn<>("Season"); - @FXML - private TreeTableColumn columnEpisode = new TreeTableColumn<>("Episode"); - @FXML - private TreeTableColumn columnFavorite = new TreeTableColumn<>("Favorite"); - - @FXML - private TreeItem sourceRoot = new TreeItem<>(new SourceDataType("", "")); - @FXML - private TableColumn sourceColumn; - @FXML - private TableColumn modeColumn; + 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 boolean settingsTrue = false; - private boolean autoUpdate = false; - private boolean useBeta = false; - private boolean autoplay = false; - private static final Logger LOGGER = LogManager.getLogger(MainWindowController.class.getName()); - private int hashA = -647380320; - private final String version = "0.7.0"; - private final String buildNumber = "151"; - private final String versionName = "toothless dragon"; - private String dialogBtnStyle; - private String color; - private String local; - private String omdbAPIKey; - - // text strings - private String errorLoad; - private String errorSave; - private String infoText; - private String vlcNotInstalled; - - private double fontSize; - private int last; - private int indexTable; - private int indexList; - private int next; - private ResourceBundle bundle; - private FilmTabelDataType currentTableFilm = new FilmTabelDataType("", "", "", "", false, false, null); - - private ObservableList languages = FXCollections.observableArrayList("English (en_US)", "Deutsch (de_DE)"); - private ObservableList branches = FXCollections.observableArrayList("stable", "beta"); - private ObservableList filterData = FXCollections.observableArrayList(); - private ObservableList filmsList = FXCollections.observableArrayList(); - private ObservableList sourcesList = FXCollections.observableArrayList(); - private ImageView skip_previous_white = new ImageView(new Image("icons/ic_skip_previous_white_18dp_1x.png")); - private ImageView skip_previous_black = new ImageView(new Image("icons/ic_skip_previous_black_18dp_1x.png")); - private ImageView skip_next_white = new ImageView(new Image("icons/ic_skip_next_white_18dp_1x.png")); - private ImageView skip_next_black = new ImageView(new Image("icons/ic_skip_next_black_18dp_1x.png")); - private ImageView play_arrow_white = new ImageView(new Image("icons/ic_play_arrow_white_18dp_1x.png")); - private ImageView play_arrow_black = new ImageView(new Image("icons/ic_play_arrow_black_18dp_1x.png")); - private MenuItem like = new MenuItem("like"); - private MenuItem dislike = new MenuItem("dislike"); // TODO one option (like or dislike) - private ContextMenu menu = new ContextMenu(like, dislike); - private Properties props = new Properties(); - - private Main main; - private MainWindowController mainWindowController; - private UpdateController updateController; - private OMDbAPIController omdbAPIController; - private DBController dbController; - - /** - * "Main" Method called in Main.java main() when starting - * Initialize other objects: Updater, dbController and ApiQuery - */ - void setMain(Main main) { - this.main = main; - mainWindowController = this; - dbController = new DBController(this.main, this); - omdbAPIController = new OMDbAPIController(this, dbController, this.main); + 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 } - // call all initialize methods - void init() { - LOGGER.info("Initializing Project-HomeFlix build " + buildNumber); - loadSettings(); - checkAutoUpdate(); - initTabel(); - initUI(); + 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(); - } - - // Initialize the tables (treeTableViewfilm and sourcesTable) - private void initTabel() { + dbController.init(); - // film Table - columnStreamUrl.setMaxWidth(0); - columnTitle.setMaxWidth(190); - columnFavorite.setMaxWidth(80); - columnSeason.setMaxWidth(73); - columnEpisode.setMaxWidth(77); - columnFavorite.setStyle("-fx-alignment: CENTER;"); - - filmsTreeTable.setRoot(filmRoot); - filmsTreeTable.setColumnResizePolicy(TreeTableView.CONSTRAINED_RESIZE_POLICY); - filmsTreeTable.setShowRoot(false); - - // write content into cell - columnStreamUrl.setCellValueFactory(cellData -> cellData.getValue().getValue().streamUrlProperty()); - columnTitle.setCellValueFactory(cellData -> cellData.getValue().getValue().titleProperty()); - columnSeason.setCellValueFactory(cellData -> cellData.getValue().getValue().seasonProperty()); - columnEpisode.setCellValueFactory(cellData -> cellData.getValue().getValue().episodeProperty()); - columnFavorite.setCellValueFactory(cellData -> cellData.getValue().getValue().imageProperty()); - - // add columns to treeTableViewfilm - filmsTreeTable.getColumns().add(columnStreamUrl); - filmsTreeTable.getColumns().add(columnTitle); - filmsTreeTable.getColumns().add(columnFavorite); - filmsTreeTable.getColumns().add(columnSeason); - filmsTreeTable.getColumns().add(columnEpisode); - filmsTreeTable.getColumns().get(0).setVisible(false); // hide columnStreamUrl (important) - - // context menu for treeTableViewfilm - filmsTreeTable.setContextMenu(menu); - - // sourcesTreeTable - sourceColumn.setCellValueFactory(cellData -> cellData.getValue().pathProperty()); - modeColumn.setCellValueFactory(cellData -> cellData.getValue().modeProperty()); - sourcesTable.setItems(sourcesList); + // 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 == false) { - sideMenuSlideIn(); - burgerTask.setRate(1.0); - burgerTask.play(); - menuTrue = true; - } else { + + if (menuTrue) { sideMenuSlideOut(); burgerTask.setRate(-1.0); burgerTask.play(); menuTrue = false; - } - if (settingsTrue == true) { - settingsScrollPane.setVisible(false); - saveSettings(); - settingsTrue = false; - } - }); - - searchTextField.textProperty().addListener(new ChangeListener() { - @Override - public void changed(ObservableValue observable, String oldValue, String newValue) { - ObservableList helpData; - filterData.clear(); - filmRoot.getChildren().removeAll(filmRoot.getChildren()); - - helpData = filmsList; - - - for (int i = 0; i < helpData.size(); i++) { - if (helpData.get(i).getTitle().toLowerCase().contains(searchTextField.getText().toLowerCase())) { - filterData.add(helpData.get(i)); // add data from newDaten to filteredData where title contains search input - } - } - - for (int i = 0; i < filterData.size(); i++) { - filmRoot.getChildren().add(new TreeItem(filterData.get(i))); // add filtered data to root node after search - } - if (searchTextField.getText().hashCode() == hashA) { - setColor("000000"); - applyColor(); - } - } - }); - - languageChoisBox.getSelectionModel().selectedIndexProperty().addListener(new ChangeListener() { - @Override - public void changed(ObservableValue ov, Number value, Number new_value) { - String local = languageChoisBox.getItems().get((int) new_value).toString(); - local = local.substring(local.length() - 6, local.length() - 1); // reading only en_US from English (en_US) - setLocal(local); - setLocalUI(); - saveSettings(); - } - }); - - branchChoisBox.getSelectionModel().selectedIndexProperty().addListener(new ChangeListener() { - @Override - public void changed(ObservableValue ov, Number value, Number new_value) { - if (branchChoisBox.getItems().get((int) new_value).toString() == "beta") { - setUseBeta(true); - } else { - setUseBeta(false); - } - saveSettings(); - } - }); - - fontsizeSlider.valueProperty().addListener(new ChangeListener() { - @Override - public void changed(ObservableValue ov, Number old_val, Number new_val) { - setFontSize(fontsizeSlider.getValue()); - if (!getCurrentTitle().isEmpty()) { - dbController.readCache(getCurrentStreamUrl()); - } - // ta1.setFont(Font.font("System", size)); - saveSettings(); - } - }); - - like.setOnAction(new EventHandler() { - @Override - public void handle(ActionEvent event) { - dbController.like(getCurrentStreamUrl()); - dbController.refresh(getCurrentStreamUrl(), indexList); - refreshTable(); - } - }); - - dislike.setOnAction(new EventHandler() { - @Override - public void handle(ActionEvent event) { - dbController.dislike(getCurrentStreamUrl()); - dbController.refresh(getCurrentStreamUrl(), indexList); - refreshTable(); - } - }); - - /** - * FIXME fix bug when sort by ASCENDING, wrong order - */ - columnFavorite.sortTypeProperty().addListener(new ChangeListener() { - @Override - public void changed(ObservableValue paramObservableValue, SortType paramT1, SortType paramT2) { - filmRoot.getChildren().clear(); - filterData.clear(); - - if (paramT2.equals(SortType.DESCENDING)) { - for (FilmTabelDataType film : filmsList) { - if (film.getFavorite()) { - filterData.add(0, film); - } else { - filterData.add(film); - } - } - } else { -// System.out.println("ascending"); - for (FilmTabelDataType film : filmsList) { - if (!film.getFavorite()) { - filterData.add(0, film); - } else { - filterData.add(film); - } - } - } - - addDataUI(filterData); - } - }); - - // Change-listener for treeTableViewfilm - filmsTreeTable.getSelectionModel().selectedItemProperty().addListener(new ChangeListener() { - @Override - public void changed(ObservableValue observable, Object oldVal, Object newVal) { - if (filmsTreeTable.getSelectionModel().getSelectedItem() == null) { - return; - } - - currentTableFilm = filmsTreeTable.getSelectionModel().getSelectedItem().getValue(); // set the current film object - indexTable = filmsTreeTable.getSelectionModel().getSelectedIndex(); // get selected items table index - for (FilmTabelDataType film : filmsList) { - if (film.equals(currentTableFilm)) { - indexList = filmsList.indexOf(film); // get selected items list index - } - } - - last = indexTable - 1; - next = indexTable + 1; - - if (currentTableFilm.getCached() || dbController.searchCache(getCurrentStreamUrl())) { - LOGGER.info("loading from cache: " + getCurrentTitle()); - dbController.readCache(getCurrentStreamUrl()); - } else { - omdbAPIController = new OMDbAPIController(mainWindowController, dbController, main); - Thread omdbAPIThread = new Thread(omdbAPIController); - omdbAPIThread.setName("OMDbAPI"); - omdbAPIThread.start(); - } - } - }); - } - - // initialize UI elements - private void initUI() { - versionLbl.setText("Version: " + version + " (Build: " + buildNumber + ")"); - fontsizeSlider.setValue(getFontSize()); - colorPicker.setValue(Color.valueOf(getColor())); - - updateBtn.setFont(Font.font("System", 12)); - autoUpdateToggleBtn.setSelected(isAutoUpdate()); - autoplayToggleBtn.setSelected(isAutoplay()); - languageChoisBox.setItems(languages); - branchChoisBox.setItems(branches); - - if (isUseBeta()) { - branchChoisBox.getSelectionModel().select(1); - } else { - branchChoisBox.getSelectionModel().select(0); - } - - setLocalUI(); - applyColor(); - } - - @FXML - private void playbtnclicked() { - if (currentTableFilm.getStreamUrl().contains("_rootNode")) { - LOGGER.info("rootNode found, getting last watched episode"); - currentTableFilm = dbController.getLastWatchedEpisode(currentTableFilm.getTitle()); - } - - if (isSupportedFormat(currentTableFilm)) { - new Player(mainWindowController); - } else { - LOGGER.error("using fallback player!"); - if (System.getProperty("os.name").contains("Linux")) { - String line; - String output = ""; - Process p; - try { - p = Runtime.getRuntime().exec("which vlc"); - BufferedReader input = new BufferedReader(new InputStreamReader(p.getInputStream())); - while ((line = input.readLine()) != null) { - output = line; - } - LOGGER.info(output); - input.close(); - } catch (IOException e1) { - e1.printStackTrace(); - } - if (output.contains("which: no vlc") || output == "") { - JFXInfoAlert vlcInfoAlert = new JFXInfoAlert("Info", vlcNotInstalled, dialogBtnStyle, main.getPrimaryStage()); - vlcInfoAlert.showAndWait(); - } else { - try { - new ProcessBuilder("vlc", getCurrentStreamUrl()).start(); - } catch (IOException e) { - LOGGER.warn("An error has occurred while opening the file!", e); - } - } - - } else if (System.getProperty("os.name").contains("Windows") || System.getProperty("os.name").contains("Mac OS X")) { - try { - Desktop.getDesktop().open(new File(getCurrentStreamUrl())); - } catch (IOException e) { - LOGGER.warn("An error has occurred while opening the file!", e); - } } else { - LOGGER.error(System.getProperty("os.name") + ", OS is not supported, please contact a developer! "); + sideMenuSlideIn(); + burgerTask.setRate(1.0); + burgerTask.play(); + menuTrue = true; } - } - } - - /** - * check if a film is supported by the HomeFlixPlayer or not - * this is the case if the mime type is mp4 - * @param entry the film you want to check - * @return true if so, false if not - */ - private boolean isSupportedFormat(FilmTabelDataType film) { - String mimeType = URLConnection.guessContentTypeFromName(film.getStreamUrl()); - return mimeType != null && (mimeType.contains("mp4") || mimeType.contains("vp6")); - } - - @FXML - private void openfolderbtnclicked() { - String dest = new File(getCurrentStreamUrl()).getParentFile().getAbsolutePath(); - if (!System.getProperty("os.name").contains("Linux")) { - try { - Desktop.getDesktop().open(new File(dest)); - } catch (IOException e) { - e.printStackTrace(); + + if (settingsViewController.isVisible()) { + settingsViewController.setVisible(false); } - } + }); } - - @FXML - private void returnBtnclicked(){ - filmsTreeTable.getSelectionModel().select(last); - } - - @FXML - private void forwardBtnclicked(){ - filmsTreeTable.getSelectionModel().select(next); - } - + + // general fxml actions @FXML private void aboutBtnAction() { - String bodyText = "cemu_UI by @Seil0 \nVersion: " + version + " (Build: " + buildNumber + ") \"" - + versionName + "\" \n" + infoText; - JFXInfoAlert infoAlert = new JFXInfoAlert("Project HomeFlix", bodyText, dialogBtnStyle, main.getPrimaryStage()); + 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(){ - if(settingsTrue == false){ - settingsScrollPane.setVisible(true); - settingsTrue = true; - }else{ - settingsScrollPane.setVisible(false); - saveSettings(); - settingsTrue = false; - } - } - - @FXML - private void addDirectoryBtnAction(){ - DirectoryChooser directoryChooser = new DirectoryChooser(); - directoryChooser.setTitle(bundle.getString("addDirectory")); - File selectedFolder = directoryChooser.showDialog(main.getPrimaryStage()); - if (selectedFolder != null && selectedFolder.exists()) { - mainWindowController.addSource(selectedFolder.getPath(), "local"); - } else { - LOGGER.error("The selected folder dosen't exist!"); - } - } - - @FXML - private void addStreamSourceBtnAction(){ - FileChooser fileChooser = new FileChooser(); - fileChooser.setTitle("addStreamSource"); - File selectedFile = fileChooser.showOpenDialog(main.getPrimaryStage()); - if (selectedFile != null && selectedFile.exists()) { - addSource(selectedFile.getPath(), "stream"); - dbController.refreshDataBase(); - } else { - LOGGER.error("The selected file dosen't exist!"); - } - } - - @FXML - private void colorPickerAction(){ - editColor(colorPicker.getValue().toString()); - applyColor(); - } - - @FXML - private void updateBtnAction(){ - updateController = new UpdateController(this, buildNumber, useBeta); - Thread updateThread = new Thread(updateController); - updateThread.setName("Updater"); - updateThread.start(); - } - - @FXML - private void autoUpdateToggleBtnAction(){ - if (isAutoUpdate()) { - setAutoUpdate(false); - } else { - setAutoUpdate(true); - } - saveSettings(); - } - - @FXML - private void autoplayToggleBtnAction(){ - if (isAutoplay()) { - setAutoplay(false); - } else { - setAutoplay(true); - } - saveSettings(); - } - - // refresh the selected child of the root node - private void refreshTable() { - filmRoot.getChildren().get(indexTable).setValue(filmsList.get(indexList)); + private void settingsBtnclicked() { + settingsViewController.setVisible(!settingsViewController.isVisible()); } /** - * add data from films-list to films-table + * 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 */ - public void addDataUI(ObservableList elementsList) { + 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); - for (FilmTabelDataType element : elementsList) { - - // only if the entry contains a season and a episode it's a valid series - if (!element.getSeason().isEmpty() && !element.getEpisode().isEmpty()) { - - // check if there is a series node to add the item - for (int i = 0; i < filmRoot.getChildren().size(); i++) { - if (filmRoot.getChildren().get(i).getValue().getTitle().equals(element.getTitle())) { - // if a root node exists, add element as child - TreeItem episodeNode = new TreeItem<>(new FilmTabelDataType( - element.getStreamUrl(), element.getTitle(), element.getSeason(), element.getEpisode(), - element.getFavorite(), element.getCached(), element.getImage())); - filmRoot.getChildren().get(i).getChildren().add(episodeNode); - } else if (filmRoot.getChildren().get(i).nextSibling() == null) { - // if no root node exists, create one and add element as child - TreeItem seriesRootNode = new TreeItem<>(new FilmTabelDataType( - element.getTitle() + "_rootNode", element.getTitle(), "", "", element.getFavorite(), - false, element.getImage())); - filmRoot.getChildren().add(seriesRootNode); - } - } + // 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 { - // if season and episode are empty, we can assume the object is a film - filmRoot.getChildren().add(new TreeItem(element)); + LOGGER.error("The selected folder dosen't exist!"); + System.exit(1); } - } - } - - // add a source to the sources table on the settings pane - public void addSourceToTable(String path, String mode) { - sourcesList.add(new SourceDataType(path, mode)); - sourceRoot.getChildren().add(new TreeItem(sourcesList.get(sourcesList.size() - 1))); // adds data to root-node - } - - // add a source to the newsources list - public void addSource(String path, String mode) { - JsonObject source = null; - JsonArray newsources = null; + }); + // 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(main.getDirectory() + "/sources.json"); - if (oldSources.exists()) { - newsources = Json.parse(new FileReader(main.getDirectory() + "/sources.json")).asArray(); - } else { - newsources = Json.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 - source = Json.object().add("path", path).add("mode", mode); + JsonObject source = Json.object().add("path", path).add("mode", mode); newsources.add(source); - Writer writer = new FileWriter(main.getDirectory() + "/sources.json"); + Writer writer = new FileWriter(XMLController.getDirHomeFlix() + "/sources.json"); newsources.writeTo(writer); writer.close(); } catch (IOException e) { - LOGGER.error(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 + * set the color of the GUI-Elements if usedColor is less than checkColor set + * text fill white, else black */ - private void applyColor() { - String style = "-fx-background-color: #" + getColor() + ";"; - String btnStyleBlack = "-fx-button-type: RAISED; -fx-background-color: #" + getColor() + "; -fx-text-fill: BLACK;"; - String btnStyleWhite = "-fx-button-type: RAISED; -fx-background-color: #" + getColor() + "; -fx-text-fill: WHITE;"; - BigInteger usedColor = new BigInteger(getColor(), 16); + void applyColor() { + String menuBtnStyle; + BigInteger usedColor = new BigInteger(XMLController.getColor(), 16); BigInteger checkColor = new BigInteger("78909cff", 16); - sideMenuVBox.setStyle(style); - topHBox.setStyle(style); - searchTextField.setFocusColor(Color.valueOf(getColor())); - + menuHam.getStyleClass().clear(); + if (usedColor.compareTo(checkColor) == -1) { - dialogBtnStyle = btnStyleWhite; - settingsBtn.setStyle("-fx-text-fill: WHITE;"); - aboutBtn.setStyle("-fx-text-fill: WHITE;"); - addDirectoryBtn.setStyle(btnStyleWhite); - addStreamSourceBtn.setStyle(btnStyleWhite); - updateBtn.setStyle(btnStyleWhite); - playbtn.setStyle(btnStyleWhite); - openfolderbtn.setStyle(btnStyleWhite); - returnBtn.setStyle(btnStyleWhite); - forwardBtn.setStyle(btnStyleWhite); - playbtn.setGraphic(play_arrow_white); - returnBtn.setGraphic(skip_previous_white); - forwardBtn.setGraphic(skip_next_white); + 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 { - dialogBtnStyle = btnStyleBlack; - settingsBtn.setStyle("-fx-text-fill: BLACK;"); - aboutBtn.setStyle("-fx-text-fill: BLACK;"); - addDirectoryBtn.setStyle(btnStyleBlack); - addStreamSourceBtn.setStyle(btnStyleBlack); - updateBtn.setStyle(btnStyleBlack); - playbtn.setStyle(btnStyleBlack); - openfolderbtn.setStyle(btnStyleBlack); - returnBtn.setStyle(btnStyleBlack); - forwardBtn.setStyle(btnStyleBlack); - playbtn.setGraphic(play_arrow_black); - returnBtn.setGraphic(skip_previous_black); - forwardBtn.setGraphic(skip_next_black); + 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() { - sideMenuVBox.setVisible(true); TranslateTransition translateTransition = new TranslateTransition(Duration.millis(400), sideMenuVBox); - translateTransition.setFromX(-150); - translateTransition.setToX(0); + 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(0); - translateTransition.setToX(-150); + translateTransition.setFromX(150); + translateTransition.setToX(0); translateTransition.play(); } - + /** * set the local based on the languageChoisBox selection */ void setLocalUI() { - switch (getLocal()) { + // TODO switch expressions + switch (XMLController.getUsrLocal()) { case "en_US": - setBundle(ResourceBundle.getBundle("locals.HomeFlix-Local", Locale.US)); // us_English - languageChoisBox.getSelectionModel().select(0); + XMLController.setLocalBundle(ResourceBundle.getBundle("locals.HomeFlix-Local", Locale.US)); // us_English break; case "de_DE": - setBundle(ResourceBundle.getBundle("locals.HomeFlix-Local", Locale.GERMAN)); // German - languageChoisBox.getSelectionModel().select(1); + XMLController.setLocalBundle(ResourceBundle.getBundle("locals.HomeFlix-Local", Locale.GERMAN)); // German break; default: - setBundle(ResourceBundle.getBundle("locals.HomeFlix-Local", Locale.US)); // default local - languageChoisBox.getSelectionModel().select(0); + XMLController.setLocalBundle(ResourceBundle.getBundle("locals.HomeFlix-Local", Locale.US)); // default local break; } - aboutBtn.setText(getBundle().getString("info")); - settingsBtn.setText(getBundle().getString("settings")); - searchTextField.setPromptText(getBundle().getString("tfSearch")); - openfolderbtn.setText(getBundle().getString("openFolder")); - updateBtn.setText(getBundle().getString("checkUpdates")); - addDirectoryBtn.setText(getBundle().getString("addDirectory")); - addStreamSourceBtn.setText(getBundle().getString("addStreamSource")); - homeflixSettingsLbl.setText(getBundle().getString("homeflixSettingsLbl")); - mainColorLbl.setText(getBundle().getString("mainColorLbl")); - fontsizeLbl.setText(getBundle().getString("fontsizeLbl")); - languageLbl.setText(getBundle().getString("languageLbl")); - autoUpdateToggleBtn.setText(getBundle().getString("autoUpdate")); - autoplayToggleBtn.setText(getBundle().getString("autoplay")); - branchLbl.setText(getBundle().getString("branchLbl")); - columnStreamUrl.setText(getBundle().getString("columnStreamUrl")); - columnTitle.setText(getBundle().getString("columnName")); - columnSeason.setText(getBundle().getString("columnSeason")); - columnEpisode.setText(getBundle().getString("columnEpisode")); - columnFavorite.setText(getBundle().getString("columnFavorite")); - errorLoad = getBundle().getString("errorLoad"); - errorSave = getBundle().getString("errorSave"); - infoText = getBundle().getString("infoText"); - vlcNotInstalled = getBundle().getString("vlcNotInstalled"); - } - - /** - * save the configuration to the config.xml file - */ - public void saveSettings() { - LOGGER.info("saving settings ..."); - try { - props.setProperty("color", getColor()); - props.setProperty("autoUpdate", String.valueOf(isAutoUpdate())); - props.setProperty("useBeta", String.valueOf(isUseBeta())); - props.setProperty("autoplay", String.valueOf(isAutoplay())); - props.setProperty("size", getFontSize().toString()); - props.setProperty("local", getLocal()); - props.setProperty("ratingSortType", columnFavorite.getSortType().toString()); - - OutputStream outputStream = new FileOutputStream(main.getConfigFile()); // new output-stream - props.storeToXML(outputStream, "Project HomeFlix settings"); // write new .xml - outputStream.close(); - } catch (IOException e) { - LOGGER.error(errorLoad, e); - } - } - - /** - * load the configuration from the config.xml file - * and try to load the API keys from apiKeys.json - */ - public void loadSettings() { - LOGGER.info("loading settings ..."); - try { - InputStream inputStream = new FileInputStream(main.getConfigFile()); - props.loadFromXML(inputStream); // new input-stream from .xml - - try { - setColor(props.getProperty("color")); - } catch (Exception e) { - LOGGER.error("cloud not load color", e); - setColor(""); - } - - try { - setFontSize(Double.parseDouble(props.getProperty("size"))); - } catch (Exception e) { - LOGGER.error("cloud not load fontsize", e); - setFontSize(17.0); - } - - try { - setAutoUpdate(Boolean.parseBoolean(props.getProperty("autoUpdate"))); - } catch (Exception e) { - LOGGER.error("cloud not load autoUpdate", e); - setAutoUpdate(false); - } - - try { - setUseBeta(Boolean.parseBoolean(props.getProperty("useBeta"))); - } catch (Exception e) { - LOGGER.error("cloud not load autoUpdate", e); - setUseBeta(false); - } - - try { - setAutoplay(Boolean.parseBoolean(props.getProperty("autoplay"))); - } catch (Exception e) { - LOGGER.error("cloud not load autoplay", e); - setAutoplay(false); - } - - try { - setLocal(props.getProperty("local")); - } catch (Exception e) { - LOGGER.error("cloud not load local", e); - setLocal(System.getProperty("user.language") + "_" + System.getProperty("user.country")); - } - - inputStream.close(); - } catch (IOException e) { - LOGGER.error(errorSave, e); - } + settingsViewController.updateGUILocal(); + filmDetailViewController.updateGUILocal(); + seriesDetailViewController.updateGUILocal(); - // try loading the omdbAPI key - try { - InputStream in = getClass().getClassLoader().getResourceAsStream("apiKeys.json"); - if (in != null) { - BufferedReader reader = new BufferedReader(new InputStreamReader(in)); - JsonObject apiKeys = Json.parse(reader).asObject(); - omdbAPIKey = apiKeys.getString("omdbAPIKey", ""); - reader.close(); - in.close(); - } else { - LOGGER.warn("Cloud not load apiKeys.json. No such file"); - } - } catch (Exception e) { - LOGGER.error("Cloud not load the omdbAPI key. Please contact the developer!", e); - } + 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 (isAutoUpdate()) { - try { - LOGGER.info("AutoUpdate: looking for updates on startup ..."); - updateController = new UpdateController(this, buildNumber, useBeta); - Thread updateThread = new Thread(updateController); - updateThread.setName("Updater"); - updateThread.start(); - updateThread.join(); - } catch (InterruptedException e) { - e.printStackTrace(); - } + if (XMLController.isAutoUpdate() && UpdateController.isUpdateAvailable()) { + UpdateController.update(); } } - - // cuts 0x of the Color-pickers return value - private void editColor(String input) { - StringBuilder sb = new StringBuilder(input); - sb.delete(0, 2); - this.color = sb.toString(); - saveSettings(); + + + /** + * Poser Mode + */ + + private void posterModeStartup() { + checkAllPosters(); + addAllPosters(); + checkCache(); } - // getter and setter - public DBController getDbController() { - return dbController; - } + /** + * 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); - public void setColor(String input) { - this.color = input; - } + for (FilmTabelDataType entry : dbController.getAllNotCachedEntries()) { +// System.out.println(entry.getStreamUrl() + " is NOT cached!"); - public String getColor() { - return color; + 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"); } - public FilmTabelDataType getCurrentTableFilm() { - return currentTableFilm; - } - - public String getCurrentTitle() { - return currentTableFilm.getTitle(); - } - - public String getCurrentStreamUrl() { - return currentTableFilm.getStreamUrl(); - } - - public void setFontSize(Double input) { - this.fontSize = input; - } - - public Double getFontSize() { - return fontSize; - } - - public int getIndexTable() { - return indexTable; + /** + * 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"); } - public int getIndexList() { - return indexList; - } - - public void setAutoUpdate(boolean input) { - this.autoUpdate = input; - } - - public boolean isAutoUpdate() { - return autoUpdate; - } - - public boolean isUseBeta() { - return useBeta; - } - - public void setUseBeta(boolean useBeta) { - this.useBeta = useBeta; + // 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(); } - public boolean isAutoplay() { - return autoplay; + private void enableBlur() { + BoxBlur boxBlur = new BoxBlur(); + boxBlur.setWidth(9); + boxBlur.setHeight(7); + boxBlur.setIterations(3); + posterModeFlowPane.setEffect(boxBlur); } - - public void setAutoplay(boolean autoplay) { - this.autoplay = autoplay; - } - - public void setLocal(String input) { - this.local = input; - } - - public String getLocal() { - return local; - } - - public String getOmdbAPIKey() { - return omdbAPIKey; - } - - public ObservableList getFilmsList() { - return filmsList; - } - - public ObservableList getSourcesList() { - return sourcesList; - } - - public ResourceBundle getBundle() { - return bundle; - } - - public void setBundle(ResourceBundle bundle) { - this.bundle = bundle; - } - - public TreeTableView getFilmsTreeTable() { - return filmsTreeTable; - } - - public TextFlow getTextFlow() { - return textFlow; - } - - public ImageView getPosterImageView() { - return posterImageView; - } - - public JFXButton getUpdateBtn() { - return updateBtn; - } - - public TreeItem getFilmRoot() { - return filmRoot; - } - - public TreeItem getSourceRoot() { - return sourceRoot; + + public void disableBlur() { + posterModeFlowPane.setEffect(null); } } diff --git a/src/main/java/kellerkinder/HomeFlix/application/SeriesDetailView.java b/src/main/java/kellerkinder/HomeFlix/application/SeriesDetailView.java new file mode 100644 index 0000000..018dab3 --- /dev/null +++ b/src/main/java/kellerkinder/HomeFlix/application/SeriesDetailView.java @@ -0,0 +1,239 @@ +/** + * 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.awt.Desktop; +import java.io.File; +import java.io.IOException; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import com.jfoenix.controls.JFXButton; + +import javafx.animation.FadeTransition; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.ChoiceBox; +import javafx.scene.control.Label; +import javafx.scene.control.ScrollPane; +import javafx.scene.control.ScrollPane.ScrollBarPolicy; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.HBox; +import javafx.scene.text.Text; +import javafx.util.Duration; +import kellerkinder.HomeFlix.controller.DBController; +import kellerkinder.HomeFlix.controller.XMLController; +import kellerkinder.HomeFlix.datatypes.SeriresDVEpisode; +import kellerkinder.HomeFlix.player.Player; + +public class SeriesDetailView { + + @FXML private AnchorPane seriesDVPane; + @FXML private ScrollPane scrollPaneEpisodes; + + @FXML private HBox hBoxEpisodes; + + @FXML private Label lblTitle; + @FXML private Label lblYear; + @FXML private Label lblScore; + + @FXML private Label lblCrew; + @FXML private Label lblDirectors; + @FXML private Label lblDirectorsInfo; + @FXML private Label lblWriters; + @FXML private Label lblWritersInfo; + @FXML private Label lblActors; + @FXML private Label lblActorsInfo; + + @FXML private JFXButton btnWishlist; + @FXML private JFXButton btnFavourite; + @FXML private JFXButton btnHide; + @FXML private JFXButton btnPlay; + @FXML private JFXButton btnDirectory; + + @FXML private ImageView wishlistIcon; + @FXML private ImageView favoriteIcon; + @FXML private ImageView imgPoster; + + @FXML private Text textPlot; + + @FXML private ChoiceBox cbSeason; + + private DBController dbController; + private static final Logger LOGGER = LogManager.getLogger(SeriesDetailView.class.getName()); + private String currentStreamURL; + + public void initialize() { + dbController = DBController.getInstance(); + seriesDVPane.setStyle("-fx-background-color: rgba(89,89,89,0.9);"); + scrollPaneEpisodes.setStyle("-fx-background-color: transparent;"); + scrollPaneEpisodes.setHbarPolicy(ScrollBarPolicy.ALWAYS); + + cbSeason.getSelectionModel().selectedIndexProperty().addListener((e, oldValue, newValue) -> { + addEpisodes(newValue.intValue() + 1); + }); + } + + @FXML + private void btnWishlistAction() { + + } + + @FXML + private void btnFavouriteAction() { + dbController.toggleFavoriteState(currentStreamURL); + + // update the favorite icon + if(dbController.getFavoriteState(currentStreamURL) == 1) { + favoriteIcon.setImage(new Image("icons/baseline_favorite_black_48dp.png")); + } else { + favoriteIcon.setImage(new Image("icons/baseline_favorite_border_black_48dp.png")); + } + } + + @FXML + private void btnHideAction() { + hidePane(); + } + + @FXML + private void btnPlayAction() { + new Player(dbController.getLastWatchedEpisode(currentStreamURL)); + } + + @FXML + private void btnDirectoryAction() { + File dest = new File(currentStreamURL).getParentFile(); + + if (!System.getProperty("os.name").contains("Linux")) { + try { + Desktop.getDesktop().open(dest); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + /** + * set the cached data of a series to the SeriesDetailView + * @param streamURL URL of the series root directory + */ + public void setSeries(String streamURL) { + currentStreamURL = streamURL; + String[] cacheInfo = dbController.readCache(streamURL); // get the cache data from the database + + // add the cache data to the GUI + lblTitle.setText(cacheInfo[0]); + lblYear.setText("(" + cacheInfo[1] + ")"); + lblScore.setText(XMLController.getLocalBundle().getString("score") + ": " + cacheInfo[15] + "%"); + + textPlot.setText(cacheInfo[11]); + + lblDirectors.setText(cacheInfo[8]); + lblWriters.setText(cacheInfo[9]); + lblActors.setText(cacheInfo[10]); + + try { + imgPoster.setImage(new Image(new File(cacheInfo[20]).toURI().toString())); + } catch (Exception e) { + imgPoster.setImage(new Image("icons/Homeflix_Poster.png")); + LOGGER.error("No Poster found, useing default."); + } + + // set the favorite correct icon + if(dbController.getFavoriteState(streamURL) == 1) { + favoriteIcon.setImage(new Image("icons/baseline_favorite_black_48dp.png")); + } else { + favoriteIcon.setImage(new Image("icons/baseline_favorite_border_black_48dp.png")); + } + + // add all seasons to the ChoiceBox + ObservableList seasons = FXCollections.observableArrayList(); + for (int i = 1; i <= new File(currentStreamURL).listFiles().length; i++) { + seasons.add("Season " + i); + } + cbSeason.getItems().clear(); + cbSeason.setItems(seasons); + cbSeason.getSelectionModel().select(0); + + // add all episodes for season 1 + addEpisodes(1); + } + + /** + * update the text of all static GUI elements of FilmDeatilView + */ + public void updateGUILocal() { + lblCrew.setText(XMLController.getLocalBundle().getString("crew")); + lblDirectorsInfo.setText(XMLController.getLocalBundle().getString("directors")); + lblWritersInfo.setText(XMLController.getLocalBundle().getString("writers")); + lblActorsInfo.setText(XMLController.getLocalBundle().getString("actors")); + } + + /** + * show the FilmDVpane + */ + public void showPane() { + seriesDVPane.setVisible(true); + FadeTransition fadeIn = new FadeTransition(Duration.millis(300), seriesDVPane); + fadeIn.setFromValue(0.3); + fadeIn.setToValue(1.0); + fadeIn.play(); + } + + /** + * hide the FilmDVpane + */ + private void hidePane() { + FadeTransition fadeOut = new FadeTransition(Duration.millis(200), seriesDVPane); + fadeOut.setFromValue(1.0); + fadeOut.setToValue(0.3); + fadeOut.play(); + + seriesDVPane.setVisible(false); + + MainWindowController.getInstance().disableBlur(); // disable blur + } + + /** + * add all episodes of one season to the ScrollPane + * @param season the season to add + */ + private void addEpisodes(int season) { + hBoxEpisodes.getChildren().clear(); + for (String[] episodePoster : dbController.getEpisodes(new File(currentStreamURL).getName(), season)) { + Image poster; + try { + poster = new Image(episodePoster[2].length() > 0 ? new File(episodePoster[2]).toURI().toString() : "icons/Homeflix_Poster.png"); + } catch (Exception e) { + poster = new Image("icons/Homeflix_Poster.png"); + } + hBoxEpisodes.getChildren().add(new SeriresDVEpisode(episodePoster[0], episodePoster[1], poster)); + } + } + +} diff --git a/src/main/java/kellerkinder/HomeFlix/application/SettingsView.java b/src/main/java/kellerkinder/HomeFlix/application/SettingsView.java new file mode 100644 index 0000000..6a5aa61 --- /dev/null +++ b/src/main/java/kellerkinder/HomeFlix/application/SettingsView.java @@ -0,0 +1,308 @@ +/** + * 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 org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import com.eclipsesource.json.Json; +import com.eclipsesource.json.JsonArray; +import com.eclipsesource.json.JsonObject; +import com.eclipsesource.json.JsonValue; +import com.jfoenix.controls.JFXButton; +import com.jfoenix.controls.JFXColorPicker; +import com.jfoenix.controls.JFXSlider; +import com.jfoenix.controls.JFXToggleButton; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.scene.control.ChoiceBox; +import javafx.scene.control.Label; +import javafx.scene.control.ScrollPane; +import javafx.scene.control.ScrollPane.ScrollBarPolicy; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TableView; +import javafx.scene.layout.AnchorPane; +import javafx.scene.paint.Color; +import javafx.stage.DirectoryChooser; +import javafx.stage.FileChooser; +import kellerkinder.HomeFlix.controller.DBController; +import kellerkinder.HomeFlix.controller.UpdateController; +import kellerkinder.HomeFlix.controller.XMLController; +import kellerkinder.HomeFlix.datatypes.SourceDataType; + +public class SettingsView { + + @FXML private ScrollPane settingsScrollPane; + @FXML private AnchorPane settingsAnchorPane; + + @FXML private Label homeflixSettingsLbl; + @FXML private Label mainColorLbl; + @FXML private Label fontsizeLbl; + @FXML private Label languageLbl; + @FXML private Label updateLbl; + @FXML private Label branchLbl; + @FXML private Label versionLbl; + @FXML private Label PlayerLbl; + @FXML private Label sourcesLbl; + + @FXML private JFXColorPicker colorPicker; + @FXML private JFXSlider fontsizeSlider; + + @FXML private ChoiceBox languageChoisBox; + @FXML private ChoiceBox branchChoisBox; + + @FXML private JFXButton updateBtn; + @FXML private JFXButton addStreamSourceBtn; + @FXML private JFXButton addDirectoryBtn; + + @FXML private JFXToggleButton autoUpdateToggleBtn; + @FXML private JFXToggleButton autoplayToggleBtn; + + @FXML private TableView sourcesTable; + @FXML private TableColumn sourceColumn; + @FXML private TableColumn modeColumn; + + private XMLController xmlController; + private static final Logger LOGGER = LogManager.getLogger(FilmDetailView.class.getName()); + private ObservableList languages = FXCollections.observableArrayList("English (en_US)", "Deutsch (de_DE)"); + private ObservableList branches = FXCollections.observableArrayList("stable", "beta"); + private static ObservableList sourcesList = FXCollections.observableArrayList(); + + public void initialize() { + xmlController = new XMLController(); + + // initialize the GUI elements + settingsScrollPane.setVbarPolicy(ScrollBarPolicy.ALWAYS); + versionLbl.setText("Version: " + Main.version + " (Build: " + Main.buildNumber + ")"); + fontsizeSlider.setValue(XMLController.getFontSize()); + colorPicker.setValue(Color.valueOf(XMLController.getColor())); + + autoUpdateToggleBtn.setSelected(XMLController.isAutoUpdate()); + autoplayToggleBtn.setSelected(XMLController.isAutoplay()); + languageChoisBox.setItems(languages); + branchChoisBox.setItems(branches); + + branchChoisBox.getSelectionModel().select(XMLController.isUseBeta() ? 1 : 0); + // TODO switch expressions + switch (XMLController.getUsrLocal()) { + case "en_US": + languageChoisBox.getSelectionModel().select(0); + break; + case "de_DE": + languageChoisBox.getSelectionModel().select(1); + break; + default: + languageChoisBox.getSelectionModel().select(0); + break; + } + + // initialize the sources table + sourceColumn.setCellValueFactory(cellData -> cellData.getValue().pathProperty()); + modeColumn.setCellValueFactory(cellData -> cellData.getValue().modeProperty()); + sourcesTable.setItems(sourcesList); + + initActions(); + loadInitSources(); + } + + private void initActions() { + languageChoisBox.getSelectionModel().selectedIndexProperty().addListener((e, oldValue, newValue) -> { + String local = languageChoisBox.getItems().get((int) newValue).toString(); + local = local.substring(local.length() - 6, local.length() - 1); // reading only en_US from English (en_US) + + XMLController.setUsrLocal(local); + xmlController.saveSettings(); + MainWindowController.getInstance().setLocalUI(); + }); + + branchChoisBox.getSelectionModel().selectedIndexProperty().addListener((e, oldValue, newValue) -> { + if (branchChoisBox.getItems().get((int) newValue).toString() == "beta") { + XMLController.setUseBeta(true); + } else { + XMLController.setUseBeta(false); + } + xmlController.saveSettings(); + }); + + fontsizeSlider.valueProperty().addListener(e -> { + XMLController.setFontSize(fontsizeSlider.getValue()); + xmlController.saveSettings(); + + // TODO add functionality for postermode + }); + } + + @FXML + private void addDirectoryBtnAction(ActionEvent event) { + DirectoryChooser directoryChooser = new DirectoryChooser(); + directoryChooser.setTitle(XMLController.getLocalBundle().getString("addDirectory")); + File selectedFolder = directoryChooser.showDialog(settingsScrollPane.getScene().getWindow()); + if (selectedFolder != null && selectedFolder.exists()) { + addSource(selectedFolder.getPath(), "local"); + } else { + LOGGER.error("The selected folder dosen't exist!"); + } + } + + @FXML + private void addStreamSourceBtnAction(ActionEvent event) { + FileChooser fileChooser = new FileChooser(); + fileChooser.setTitle(XMLController.getLocalBundle().getString("addStreamSource")); + File selectedFile = fileChooser.showOpenDialog(settingsScrollPane.getScene().getWindow()); + if (selectedFile != null && selectedFile.exists()) { + addSource(selectedFile.getPath(), "stream"); + } else { + LOGGER.error("The selected file dosen't exist!"); + } + } + + @FXML + private void autoUpdateToggleBtnAction(ActionEvent event) { + XMLController.setAutoUpdate(!XMLController.isAutoUpdate()); + xmlController.saveSettings(); + } + + @FXML + private void autoplayToggleBtnAction(ActionEvent event) { + XMLController.setAutoplay(!XMLController.isAutoplay()); + xmlController.saveSettings(); + } + + @FXML + private void colorPickerAction(ActionEvent event) { + XMLController.setColor(colorPicker.getValue().toString().substring(2, 10)); + xmlController.saveSettings(); + MainWindowController.getInstance().applyColor(); + } + + @FXML + private void updateBtnAction(ActionEvent event) { + + + new UpdateController(); + if (UpdateController.isUpdateAvailable()) { + updateBtn.setText(XMLController.getLocalBundle().getString("updateBtnUpdateAvailable")); + UpdateController.update(); + } else { + updateBtn.setText(XMLController.getLocalBundle().getString("updateBtnNoUpdateAvailable")); + } + + } + + /** TODO can this be done async? + * add a source to the sources file and load all new streams + * @param path to the source + * @param mode of the source (local or streaming) + */ + void addSource(String path, String mode) { + JsonArray newsources = null; + + try {settingsScrollPane.setVbarPolicy(ScrollBarPolicy.ALWAYS); + // 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(e); + } + + // update the sourcesTable + sourcesList.add(new SourceDataType(path, mode)); + + DBController.getInstance().refreshDataBase(); // refreshes the database after a source path was added + MainWindowController.getInstance().checkAllPosters(); // check if there is anything new to cache + MainWindowController.getInstance().addAllPosters(); + } + + // add a all elements of sourcesList to the sources table on the settings pane + void loadInitSources() { + if(new File(XMLController.getDirHomeFlix() + "/sources.json").exists()) { + try { + // create a JsonArray, containing all sources, add each source to the mwc, get all films from it + JsonArray sources = Json.parse(new FileReader(XMLController.getDirHomeFlix() + "/sources.json")).asArray(); + for (JsonValue source : sources) { + String path = source.asObject().getString("path", ""); + String mode = source.asObject().getString("mode", ""); + sourcesList.add(new SourceDataType(path, mode)); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + public void updateColor(String btnStyle) { + updateBtn.setStyle(btnStyle); + addDirectoryBtn.setStyle(btnStyle); + addStreamSourceBtn.setStyle(btnStyle); + + autoUpdateToggleBtn.setToggleColor(Color.valueOf(XMLController.getColor())); + autoUpdateToggleBtn.setToggleLineColor(Color.valueOf(XMLController.getColor())); + autoplayToggleBtn.setToggleColor(Color.valueOf(XMLController.getColor())); + autoplayToggleBtn.setToggleLineColor(Color.valueOf(XMLController.getColor())); + } + + public void updateGUILocal() { + homeflixSettingsLbl.setText(XMLController.getLocalBundle().getString("homeflixSettingsLbl")); + mainColorLbl.setText(XMLController.getLocalBundle().getString("mainColorLbl")); + fontsizeLbl.setText(XMLController.getLocalBundle().getString("fontsizeLbl")); + languageLbl.setText(XMLController.getLocalBundle().getString("languageLbl")); + branchLbl.setText(XMLController.getLocalBundle().getString("branchLbl")); + sourcesLbl.setText(XMLController.getLocalBundle().getString("sourcesLbl")); + + updateBtn.setText(XMLController.getLocalBundle().getString("checkUpdates")); + addDirectoryBtn.setText(XMLController.getLocalBundle().getString("addDirectory")); + addStreamSourceBtn.setText(XMLController.getLocalBundle().getString("addStreamSource")); + + autoUpdateToggleBtn.setText(XMLController.getLocalBundle().getString("autoUpdate")); + autoplayToggleBtn.setText(XMLController.getLocalBundle().getString("autoplay")); + } + + public void setVisible(boolean visible) { + settingsScrollPane.setVisible(visible); + } + + public boolean isVisible() { + return settingsScrollPane.isVisible(); + } + + public JFXButton getUpdateBtn() { + return updateBtn; + } + +} diff --git a/src/main/java/kellerkinder/HomeFlix/controller/DBController.java b/src/main/java/kellerkinder/HomeFlix/controller/DBController.java index 8ef2aac..d0ba3db 100644 --- a/src/main/java/kellerkinder/HomeFlix/controller/DBController.java +++ b/src/main/java/kellerkinder/HomeFlix/controller/DBController.java @@ -1,7 +1,7 @@ /** * Project-HomeFlix * - * Copyright 2016-2018 <@Seil0> + * 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 @@ -18,76 +18,66 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ + package kellerkinder.HomeFlix.controller; import java.io.File; import java.io.FileNotFoundException; -import java.io.FileReader; import java.io.IOException; import java.net.URLConnection; import java.sql.Connection; +import java.sql.Date; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; +import java.time.LocalDate; import java.util.ArrayList; import java.util.List; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import com.eclipsesource.json.Json; -import com.eclipsesource.json.JsonArray; -import com.eclipsesource.json.JsonObject; -import com.eclipsesource.json.JsonValue; - +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; import javafx.scene.image.Image; -import javafx.scene.image.ImageView; -import javafx.scene.text.Font; -import javafx.scene.text.FontWeight; -import javafx.scene.text.Text; -import kellerkinder.HomeFlix.application.Main; -import kellerkinder.HomeFlix.application.MainWindowController; -import kellerkinder.HomeFlix.datatypes.SourceDataType; +import kellerkinder.HomeFlix.datatypes.DatabaseDataType; import kellerkinder.HomeFlix.datatypes.FilmTabelDataType; +import kellerkinder.HomeFlix.datatypes.OMDbAPIResponseDataType; +import kellerkinder.HomeFlix.datatypes.PosterModeElement; public class DBController { - private MainWindowController mainWindowController; - private Main main; - private String DB_PATH = System.getProperty("user.home") + "\\Documents\\HomeFlix" + "\\" + "Homeflix.db"; //path to database file - private Image favorite_black = new Image("icons/ic_favorite_black_18dp_1x.png"); - private Image favorite_border_black = new Image("icons/ic_favorite_border_black_18dp_1x.png"); - private List filmsdbAll = new ArrayList(); - private List filmsdbDir = new ArrayList(); - private List filmsdbStreamURL = new ArrayList(); // needed - private List filmsStreamURL = new ArrayList(); // needed + private static DBController instance = null; + private String DB_PATH; + private List databaseStreams = new ArrayList(); // contains all films stored in the database + private List sourceStreams = new ArrayList(); // contains all films from the sources private Connection connection = null; private static final Logger LOGGER = LogManager.getLogger(DBController.class.getName()); /** * constructor for DBController - * @param main the Main object - * @param mainWindowController the MainWindowController object */ - public DBController(Main main, MainWindowController mainWindowController) { - this.main = main; - this.mainWindowController = mainWindowController; + public DBController() { + // Auto-generated constructor stub + } + + public static DBController getInstance() { + if (instance == null) { + instance = new DBController(); + } + + return instance; } /** - * initialize the {@link DBController} - * initialize the database connection - * check if there is a need to create a new database - * refresh the database + * initialize the {@link DBController} with the database connection check if + * there is a need to create a new database refresh the database */ public void init() { - LOGGER.info("<========== starting loading sql ==========>"); initDatabaseConnection(); createDatabase(); - refreshDataBase(); - LOGGER.info("<========== finished loading sql ==========>"); } /** @@ -95,16 +85,16 @@ public class DBController { * AutoCommit is set to false to prevent some issues, so manual commit is active! */ private void initDatabaseConnection() { - DB_PATH = main.getDirectory() + "/Homeflix.db"; + DB_PATH = XMLController.getDirHomeFlix() + "/Homeflix.db"; try { // create a database connection connection = DriverManager.getConnection("jdbc:sqlite:" + DB_PATH); connection.setAutoCommit(false); } catch (SQLException e) { // if the error message is "out of memory", it probably means no database file is found - LOGGER.error("error while loading the ROM database", e); + LOGGER.error("error while loading the HomeFlix database", e); } - LOGGER.info("ROM database loaded successfull"); + LOGGER.info("HomeFlix database loaded successfull"); } /** @@ -112,14 +102,15 @@ public class DBController { * films table: streamUrl is primary key * cache table: streamUrl is primary key */ - private void createDatabase() { + private void createDatabase() { try { Statement stmt = connection.createStatement(); stmt.executeUpdate("create table if not exists films (streamUrl, title, season, episode, favorite, cached, currentTime)"); stmt.executeUpdate("create table if not exists cache (" - + "streamUrl, Title, Year, Rated, Released, Runtime, Genre, Director, Writer," - + " Actors, Plot, Language, Country, Awards, Metascore, imdbRating, imdbVotes," - + " imdbID, Type, Poster, Response)"); + + "streamUrl, Title, Year, Rated, Released, Season, Episode ,Runtime, Genre, Director, Writer," + + " Actors, Plot, Language, Country, Awards, Poster, Metascore, imdbRating, imdbVotes," + + " imdbID, Type, dvd, BoxOffice, Website, Response)"); + connection.commit(); stmt.close(); } catch (SQLException e) { LOGGER.error(e); @@ -135,95 +126,40 @@ public class DBController { Statement stmt = connection.createStatement(); ResultSet rs = stmt.executeQuery("SELECT * FROM films"); while (rs.next()) { - filmsdbDir.add(rs.getString("title")); - filmsdbStreamURL.add(rs.getString("streamUrl")); + databaseStreams.add(new DatabaseDataType(rs.getString("streamUrl"), + rs.getString("title"), rs.getString("season"), rs.getString("episode"), + rs.getInt("favorite"), rs.getDouble("currentTime"))); } stmt.close(); rs.close(); } catch (SQLException e) { LOGGER.error("Ups! an error occured!", e); } - - // add all entries to filmsAll and filmsdbAl, for later comparing - filmsdbAll.addAll(filmsdbDir); - LOGGER.info("films in directory: " + filmsStreamURL.size()); - LOGGER.info("filme in db: " + filmsdbStreamURL.size()); } /** - * load sources from sources.json - * if mode == local, get all files and series-folder from the directory - * else mode must be streaming, read all entries from the streaming file + * load all source streams */ private void loadSources() { - // remove sources from table - mainWindowController.getSourcesList().removeAll(mainWindowController.getSourcesList()); - mainWindowController.getSourceRoot().getChildren().removeAll(mainWindowController.getSourceRoot().getChildren()); - - try { - JsonArray sources = Json.parse(new FileReader(main.getDirectory() + "/sources.json")).asArray(); - for (JsonValue source : sources) { - String path = source.asObject().getString("path", ""); - String mode = source.asObject().getString("mode", ""); - mainWindowController.addSourceToTable(path, mode); // add source to source-table - if (mode.equals("local")) { - for (File file : new File(path).listFiles()) { - if (file.isFile() && isVideoFile(file.getPath())) { - filmsStreamURL.add(file.getPath()); - } else if(file.isDirectory()) { - // get all folders (series) - for (File season : file.listFiles()) { - if (season.isDirectory()) { - for (File episode : season.listFiles()) { - if (!filmsdbStreamURL.contains(episode.getPath())) { - filmsStreamURL.add(episode.getPath()); - } - } - } - } - } - } - LOGGER.info("added files from: " + path); - } else { - // getting all entries from the streaming lists - try { - JsonObject object = Json.parse(new FileReader(path)).asObject(); - JsonArray items = object.get("entries").asArray(); - for (JsonValue item : items) { - filmsStreamURL.add(item.asObject().getString("streamUrl", "")); - } - LOGGER.info("added films from: " + path); - } catch (IOException e) { - LOGGER.error(e); - } - } - } - } catch (Exception e) { - e.printStackTrace(); - } + SourcesController sourcesController = new SourcesController(); + sourceStreams = sourcesController.loadSources(); } /** - * load the data to the mainWindowController - * order entries by title + * load all streams from the database to a ObservableList, order entries by title + * @return a ObservableList that contains all streams from the database */ - private void loadDataToMWC() { - LOGGER.info("loading data to mwc ..."); + public ObservableList getStreamsList() { + ObservableList filmsList = FXCollections.observableArrayList(); + LOGGER.info("loading data from database ..."); try { //load local Data Statement stmt = connection.createStatement(); - ResultSet rs = stmt.executeQuery("SELECT * FROM films ORDER BY title"); - while (rs.next()) { -// System.out.println(rs.getString("title") + "Season:" + rs.getString("season") + ":"); - if (rs.getBoolean("favorite") == true) { - mainWindowController.getFilmsList().add(new FilmTabelDataType(rs.getString("streamUrl"), - rs.getString("title"), rs.getString("season"), rs.getString("episode") ,rs.getBoolean("favorite"), - rs.getBoolean("cached"), new ImageView(favorite_black))); - } else { - mainWindowController.getFilmsList().add(new FilmTabelDataType(rs.getString("streamUrl"), - rs.getString("title"), rs.getString("season"), rs.getString("episode"), rs.getBoolean("favorite"), - rs.getBoolean("cached"), new ImageView(favorite_border_black))); - } + ResultSet rs = stmt.executeQuery("SELECT * FROM films ORDER BY title"); + + while (rs.next()) { + filmsList.add(new FilmTabelDataType(rs.getString("streamUrl"), + rs.getString("title"), rs.getString("season"), rs.getString("episode"))); } stmt.close(); rs.close(); @@ -231,56 +167,81 @@ public class DBController { LOGGER.error("Ups! an error occured!", e); } - LOGGER.info("loading data to the GUI ..."); - mainWindowController.addDataUI(mainWindowController.getFilmsList()); + return filmsList; } /** - * refresh data in mainWindowController for one element - * @param streamUrl of the film - * @param index of the film in LocalFilms list + * get one stream from the database with streamUrl = ? + * @param streamUrl of the stream + * @return a FilmTabelDataType Object of the stream with the given streamUrl */ - public void refresh(String streamUrl, int indexList) { - LOGGER.info("refresh ..."); + public FilmTabelDataType getStream(String streamUrl) { + FilmTabelDataType film = null; + LOGGER.info("refresh data for " + streamUrl); try { - Statement stmt = connection.createStatement(); - ResultSet rs = stmt.executeQuery("SELECT * FROM films WHERE streamUrl = \"" + streamUrl + "\";"); + PreparedStatement ps = connection.prepareStatement("SELECT * FROM films WHERE streamUrl = ?"); + ps.setString(1, streamUrl); + ResultSet rs = ps.executeQuery(); while (rs.next()) { - if (rs.getBoolean("favorite") == true) { - mainWindowController.getFilmsList().set(indexList, new FilmTabelDataType(rs.getString("streamUrl"), - rs.getString("title"), rs.getString("season"), rs.getString("episode"), rs.getBoolean("favorite"), - rs.getBoolean("cached"), new ImageView(favorite_black))); - } else { - mainWindowController.getFilmsList().set(indexList, new FilmTabelDataType(rs.getString("streamUrl"), - rs.getString("title"), rs.getString("season"), rs.getString("episode"), rs.getBoolean("favorite"), - rs.getBoolean("cached"), new ImageView(favorite_border_black))); - } + film = new FilmTabelDataType(rs.getString("streamUrl"), + rs.getString("title"), rs.getString("season"), rs.getString("episode")); } rs.close(); - stmt.close(); + ps.close(); } catch (Exception e) { LOGGER.error("Ups! error while refreshing mwc!", e); - } + } + + return film; + } + + /** + * get entries which have no season and episode eg. root or movie of the database as PosterModeElement + * @return a ObservableList of PosterModeElements + */ + public ObservableList getPosterElementsList() { + + ObservableList posterElementsList = FXCollections.observableArrayList(); + LOGGER.info("loading data from database ..."); + try { + //load local Data + Statement stmt = connection.createStatement(); + ResultSet rs = stmt.executeQuery("SELECT * FROM films WHERE season = '' OR season = '0' ORDER BY title"); + + while (rs.next()) { + String[] cacheData = readCache(rs.getString("streamUrl")); // get from the cache table + + if(cacheData[20] != null && cacheData[20].length() > 0) { + posterElementsList.add(new PosterModeElement(rs.getString("streamUrl"), cacheData[0], new Image(new File(cacheData[20]).toURI().toString()))); + } else { + posterElementsList.add(new PosterModeElement(rs.getString("streamUrl"), cacheData[0], new Image("icons/Homeflix_Poster.png"))); + } + } + stmt.close(); + rs.close(); + } catch (SQLException e) { + LOGGER.error("An error occured while getting all PosterElements!", e); + } + + return posterElementsList; } /** * refresh database to contain all (new added) entries - * refresh the MainWindowController content, - * to contain all (new added) entries from the database */ public void refreshDataBase() { - LOGGER.info("refreshing the Database ..."); + LOGGER.info("<========== starting refreshing database ==========>"); // clean all ArraLists - filmsdbAll.clear(); - filmsdbDir.clear(); - filmsdbStreamURL.clear(); - filmsStreamURL.clear(); + databaseStreams.clear(); + sourceStreams.clear(); loadSources(); // reload all sources loadDatabase(); // reload all films saved in the DB + LOGGER.info("There are {} entries in the Database", databaseStreams.size()); + try { checkAddEntry(); checkRemoveEntry(); @@ -288,34 +249,37 @@ public class DBController { LOGGER.error("Error while refreshing the database", e); } - // remove all films from the mwc lists - mainWindowController.getFilmsList().removeAll(mainWindowController.getFilmsList()); - mainWindowController.getFilmRoot().getChildren().removeAll(mainWindowController.getFilmRoot().getChildren()); - - loadDataToMWC(); // load the new data to the mwc + LOGGER.info("<========== finished refreshing database ==========>"); } /** * check if there are any entries that have been removed from the film-directory + * @throws SQLException */ - private void checkRemoveEntry() { + private void checkRemoveEntry() throws SQLException { + PreparedStatement ps = connection.prepareStatement("DELETE FROM films WHERE streamUrl = ?"); LOGGER.info("checking for entrys to remove to DB ..."); - try { - Statement stmt = connection.createStatement(); + + for (DatabaseDataType dbStreamEntry : databaseStreams) { + // if the directory doen't contain the entry form the database, remove it - for (String entry : filmsdbStreamURL) { - // if the directory doen't contain the entry form the db, remove it - if (!filmsStreamURL.contains(entry)) { - stmt.executeUpdate("delete from films where streamUrl = \"" + entry + "\""); - connection.commit(); - LOGGER.info("removed \"" + entry + "\" from database"); - } + // if sourceStreams has a item where StreamUrl equals dbStreamEntry.getStreamUrl() return it, else null + DatabaseDataType result = sourceStreams.stream() + .filter(x -> dbStreamEntry.getStreamUrl().equals(x.getStreamUrl())) + .findAny() + .orElse(null); + + // if the result is null, the file is missing, remove it from the database + if (result == null) { + ps.setString(1, dbStreamEntry.getStreamUrl()); + ps.addBatch(); + LOGGER.info("removed \"" + dbStreamEntry.getTitle() + "\" from database"); } - - stmt.close(); - } catch (Exception e) { - LOGGER.error(e); } + + ps.executeBatch(); + connection.commit(); + ps.close(); } /** @@ -325,84 +289,36 @@ public class DBController { * @throws IOException */ private void checkAddEntry() throws SQLException, FileNotFoundException, IOException { - Statement stmt = connection.createStatement(); PreparedStatement ps = connection.prepareStatement("insert into films values (?, ?, ?, ?, ?, ?, ?)"); LOGGER.info("checking for entrys to add to DB ..."); - // source is a single source of the sources list - for (SourceDataType source : mainWindowController.getSourcesList()) { - // if it's a local source check the folder for new film - if (source.getMode().equals("local")) { - for (File file : new File(source.getPath()).listFiles()) { - String mimeType = URLConnection.guessContentTypeFromName(file.getPath()); - // if file is file and has mime type "video" - if (file.isFile() && mimeType != null && mimeType.contains("video")) { - // get all files (films) - if (!filmsdbStreamURL.contains(file.getPath())) { - stmt.executeUpdate("insert into films values (" - + "'" + file.getPath() + "'," - + "'" + cutOffEnd(file.getName()) + "', '', '', 0, 0, 0.0)"); - connection.commit(); - stmt.close(); - LOGGER.info("Added \"" + file.getName() + "\" to database"); - filmsdbStreamURL.add(file.getPath()); - } - } else if (file.isDirectory()) { - // get all folders (series) - int sn = 1; - for (File season : file.listFiles()) { - if (season.isDirectory()) { - int ep = 1; - for (File episode : season.listFiles()) { - if (!filmsdbStreamURL.contains(episode.getPath())) { - LOGGER.info("Added \"" + file.getName() + "\", Episode: " + episode.getName() + " to database"); - stmt.executeUpdate("insert into films values (" - + "'" + episode.getPath().replace("'", "''") + "'," - + "'" + cutOffEnd(file.getName()) + "','" + sn + "','" + ep + "', 0, 0, 0.0)"); - connection.commit(); - stmt.close(); - filmsStreamURL.add(episode.getPath()); - filmsdbStreamURL.add(episode.getPath()); - ep++; - } - } - sn++; - } - } - } - - } - } else { - // if it's a streaming source check the file for new films - for (String entry : filmsStreamURL) { - if (!filmsdbStreamURL.contains(entry)) { - JsonArray items = Json.parse(new FileReader(source.getPath())).asObject().get("entries").asArray(); - // for each item, check if it's the needed - for (JsonValue item : items) { - String streamUrl = item.asObject().getString("streamUrl", ""); - String title = item.asObject().getString("title", ""); - - // if it's the needed add it to the database - if (streamUrl.equals(entry)) { - ps.setString(1, streamUrl); - ps.setString(2, title); - ps.setString(3, item.asObject().getString("season", "")); - ps.setString(4, item.asObject().getString("episode", "")); - ps.setInt(5, 0); - ps.setBoolean(6, false); - ps.setDouble(7, 0); - ps.addBatch(); // adds the entry - LOGGER.info("Added \"" + title + "\" to database"); - filmsdbStreamURL.add(streamUrl); - } - } - } - } - ps.executeBatch(); - connection.commit(); - ps.close(); + // new + for (DatabaseDataType sourceStreamEntry : sourceStreams) { + + // if databaseStream has a item where StreamUrl equals sourceStreamEntry.getStreamUrl() return it, else null + DatabaseDataType result = databaseStreams.stream() + .filter(x -> sourceStreamEntry.getStreamUrl().equals(x.getStreamUrl())) + .findAny() + .orElse(null); + + // if the result is null, the entry is missing, add it to the database + if (result == null) { + ps.setString(1, sourceStreamEntry.getStreamUrl()); + ps.setString(2, sourceStreamEntry.getTitle()); + ps.setString(3, sourceStreamEntry.getSeason()); + ps.setString(4, sourceStreamEntry.getEpisode()); + ps.setInt(5, sourceStreamEntry.getFavorite()); + ps.setDate(6, new Date(0)); + ps.setDouble(7, sourceStreamEntry.getCurrentTime()); + ps.addBatch(); // adds the entry + LOGGER.info("Added \"" + sourceStreamEntry.getTitle() + "\" to database"); + databaseStreams.add(sourceStreamEntry); } } + + ps.executeBatch(); + connection.commit(); + ps.close(); } /** @@ -423,13 +339,57 @@ public class DBController { System.out.println(rs.getString("cached")); System.out.println(rs.getString("currentTime") + "\n"); } - stmt.close(); rs.close(); + stmt.close(); } catch (SQLException e) { LOGGER.error("An error occured, while printing all entries", e); } } + /** + * return the favorite state for a stream + * @param streamURL URL of the stream + * @return 0 if it's not a favorite, else 1 + */ + public int getFavoriteState(String streamURL) { + int favoriteState = 0; + PreparedStatement ps; + try { + ps = connection.prepareStatement("SELECT favorite FROM films WHERE streamUrl = ?"); + ps.setString(1, streamURL); + ResultSet rs = ps.executeQuery(); + + while (rs.next()) { + favoriteState = rs.getInt("favorite"); + } + + rs.close(); + ps.close(); + } catch (SQLException e) { + LOGGER.error("Ups! an error occured!", e); + } + + return favoriteState; + } + + public void toggleFavoriteState(String streamURL) { + PreparedStatement ps; + try { + if (getFavoriteState(streamURL) == 0) { + ps = connection.prepareStatement("UPDATE films SET favorite = 1 WHERE streamUrl = ?"); + } else { + ps = connection.prepareStatement("UPDATE films SET favorite = 0 WHERE streamUrl = ?"); + } + + ps.setString(1, streamURL); + ps.executeUpdate(); + connection.commit(); + ps.close(); + } catch (SQLException e) { + LOGGER.error("Ups! an error occured!", e); + } + } + /** * update the database entry for the given film, favorite = 0 * @param streamUrl URL of the film @@ -437,10 +397,11 @@ public class DBController { public void dislike(String streamUrl) { LOGGER.info("dislike " + streamUrl); try { - Statement stmt = connection.createStatement(); - stmt.executeUpdate("UPDATE films SET favorite=0 WHERE streamUrl=\"" + streamUrl + "\";"); + PreparedStatement ps = connection.prepareStatement("UPDATE films SET favorite = 0 WHERE streamUrl = ?"); + ps.setString(1, streamUrl); + ps.executeUpdate(); connection.commit(); - stmt.close(); + ps.close(); } catch (SQLException e) { LOGGER.error("Ups! an error occured!", e); } @@ -453,10 +414,11 @@ public class DBController { public void like(String streamUrl) { LOGGER.info("like " + streamUrl); try { - Statement stmt = connection.createStatement(); - stmt.executeUpdate("UPDATE films SET favorite=1 WHERE streamUrl=\"" + streamUrl + "\";"); + PreparedStatement ps = connection.prepareStatement("UPDATE films SET favorite = 1 WHERE streamUrl = ?"); + ps.setString(1, streamUrl); + ps.executeUpdate(); connection.commit(); - stmt.close(); + ps.close(); } catch (SQLException e) { LOGGER.error("Ups! an error occured!", e); } @@ -466,77 +428,84 @@ public class DBController { * update the database entry for the given film, cached = 1 * @param streamUrl URL of the film */ - void setCached(String streamUrl) { + public void setCached(String streamUrl) { try { - Statement stmt = connection.createStatement(); - stmt.executeUpdate("UPDATE films SET cached=1 WHERE streamUrl=\"" + streamUrl + "\";"); + PreparedStatement ps = connection.prepareStatement("UPDATE films SET cached = ? WHERE streamUrl = ?"); + ps.setDate(1, new Date(System.currentTimeMillis())); + ps.setString(2, streamUrl); + ps.executeUpdate(); connection.commit(); - stmt.close(); + ps.close(); + } catch (SQLException e) { + LOGGER.error("Ups! an error occured!", e); + } + } + + /** + * get the cached date for the film + * @param streamUrl URL of the film + */ + public LocalDate getCacheDate(String streamUrl) { + LocalDate cacheDate = LocalDate.now().minusDays(31); + try { + PreparedStatement ps = connection.prepareStatement("SELECT cached FROM films WHERE streamUrl = ?"); + ps.setString(1, streamUrl); + ResultSet rs = ps.executeQuery(); + + while (rs.next()) { + cacheDate = rs.getDate("cached").toLocalDate(); + } + + rs.close(); + ps.close(); } catch (SQLException e) { LOGGER.error("Ups! an error occured!", e); } - refresh(streamUrl, mainWindowController.getIndexList()); + return cacheDate; } /** * add the received data to the cache table - * @param streamUrl URL of the film - * @param Title - * @param Year - * @param Rated - * @param Released - * @param Runtime - * @param Genre - * @param Director - * @param Writer - * @param Actors - * @param Plot - * @param Language - * @param Country - * @param Awards - * @param Metascore - * @param imdbRating - * @param Type - * @param imdbVotes - * @param imdbID - * @param Poster - * @param Response - * @throws SQLException + * @param streamUrl URL of the film + * @param omdbResponse the response data from omdbAPI */ - void addCache( String streamUrl, String Title, String Year, String Rated, String Released, String Runtime, String Genre, String Director, - String Writer, String Actors, String Plot, String Language, String Country, String Awards, String Metascore, String imdbRating, - String Type, String imdbVotes, String imdbID, String Poster, String Response) { + public void addCache(String streamUrl, OMDbAPIResponseDataType omdbResponse) { try { - PreparedStatement ps = connection.prepareStatement("insert into cache values (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"); + PreparedStatement ps = connection.prepareStatement("insert into cache values (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"); - LOGGER.info("adding to cache: " + Title); + LOGGER.info("adding cache for: " + streamUrl); ps.setString(1,streamUrl); - ps.setString(2,Title); - ps.setString(3,Year); - ps.setString(4,Rated); - ps.setString(5,Released); - ps.setString(6,Runtime); - ps.setString(7,Genre); - ps.setString(8,Director); - ps.setString(9,Writer); - ps.setString(10,Actors); - ps.setString(11,Plot); - ps.setString(12,Language); - ps.setString(13,Country); - ps.setString(14,Awards); - ps.setString(15,Metascore); - ps.setString(16,imdbRating); - ps.setString(17,imdbVotes); - ps.setString(18,imdbID); - ps.setString(19,Type); - ps.setString(20,Poster); - ps.setString(21,Response); + ps.setString(2,omdbResponse.getTitle()); + ps.setString(3,omdbResponse.getYear()); + ps.setString(4,omdbResponse.getRated()); + ps.setString(5,omdbResponse.getReleased()); + ps.setString(6,omdbResponse.getSeason()); + ps.setString(7,omdbResponse.getEpisode()); + ps.setString(8,omdbResponse.getRuntime()); + ps.setString(9,omdbResponse.getGenre()); + ps.setString(10,omdbResponse.getDirector()); + ps.setString(11,omdbResponse.getWriter()); + ps.setString(12,omdbResponse.getActors()); + ps.setString(13,omdbResponse.getPlot()); + ps.setString(14,omdbResponse.getLanguage()); + ps.setString(15,omdbResponse.getCountry()); + ps.setString(16,omdbResponse.getAwards()); + ps.setString(17,omdbResponse.getPoster()); + ps.setString(18,omdbResponse.getMetascore()); + ps.setString(19,omdbResponse.getImdbRating()); + ps.setString(20,omdbResponse.getImdbVotes()); + ps.setString(21,omdbResponse.getImdbID()); + ps.setString(22,omdbResponse.getType()); + ps.setString(23,omdbResponse.getDvd()); + ps.setString(24,omdbResponse.getBoxOffice()); + ps.setString(25,omdbResponse.getWebsite()); + ps.setString(26,omdbResponse.getResponse()); + ps.addBatch(); ps.executeBatch(); connection.commit(); ps.close(); - LOGGER.info("done!"); } catch (Exception e) { LOGGER.error(e); } @@ -547,14 +516,15 @@ public class DBController { * @param streamUrl URL of the element * @return true if the element is already cached, else false */ - public boolean searchCache(String streamUrl) { + public boolean searchCacheByURL(String streamUrl) { boolean retValue = false; try { - Statement stmt = connection.createStatement(); - ResultSet rs = stmt.executeQuery("SELECT * FROM cache WHERE streamUrl = \"" + streamUrl + "\";"); + PreparedStatement ps = connection.prepareStatement("SELECT * FROM cache WHERE streamUrl = ?"); + ps.setString(1, streamUrl); + ResultSet rs = ps.executeQuery(); retValue = rs.next(); rs.close(); - stmt.close(); + ps.close(); } catch (Exception e) { LOGGER.error("Ups! error while getting the current time!", e); } @@ -563,70 +533,73 @@ public class DBController { } /** - * sets the cached data to mwc's TextFlow + * read the cached data from the Database * @param streamUrl URL of the film + * @return a String array (length = 21) with all cached data */ - public void readCache(String streamUrl) { + public String[] readCache(String streamUrl) { + String[] cacheData = new String[21]; + try { + PreparedStatement ps = connection.prepareStatement("SELECT * FROM cache WHERE streamUrl = ?"); + ps.setString(1, streamUrl); + ResultSet rs = ps.executeQuery(); - Statement stmt = connection.createStatement(); - ResultSet rs = stmt.executeQuery("SELECT * FROM cache WHERE streamUrl=\"" + streamUrl + "\";"); - ArrayList nameText = new ArrayList(); - ArrayList responseText = new ArrayList(); - Image im; - int fontSize = (int) Math.round(mainWindowController.getFontSize()); - int j = 2; - - nameText.add(0, new Text(mainWindowController.getBundle().getString("title") + ": ")); - nameText.add(1, new Text(mainWindowController.getBundle().getString("year") + ": ")); - nameText.add(2, new Text(mainWindowController.getBundle().getString("rating") + ": ")); - nameText.add(3, new Text(mainWindowController.getBundle().getString("publishedOn") + ": ")); - nameText.add(4, new Text(mainWindowController.getBundle().getString("duration") + ": ")); - nameText.add(5, new Text(mainWindowController.getBundle().getString("genre") + ": ")); - nameText.add(6, new Text(mainWindowController.getBundle().getString("director") + ": ")); - nameText.add(7, new Text(mainWindowController.getBundle().getString("writer") + ": ")); - nameText.add(8, new Text(mainWindowController.getBundle().getString("actors") + ": ")); - nameText.add(9, new Text(mainWindowController.getBundle().getString("plot") + ": ")); - nameText.add(10, new Text(mainWindowController.getBundle().getString("language") + ": ")); - nameText.add(11, new Text(mainWindowController.getBundle().getString("country") + ": ")); - nameText.add(12, new Text(mainWindowController.getBundle().getString("awards") + ": ")); - nameText.add(13, new Text(mainWindowController.getBundle().getString("metascore") + ": ")); - nameText.add(14, new Text(mainWindowController.getBundle().getString("imdbRating") + ": ")); - nameText.add(15, new Text(mainWindowController.getBundle().getString("type") + ": ")); - - for (int i = 0; i < 15; i++) { - responseText.add(new Text(rs.getString(j) + "\n")); - j++; + while (rs.next()) { + cacheData[0] = rs.getString("Title"); + cacheData[1] = rs.getString("Year"); + cacheData[2] = rs.getString("Rated"); + cacheData[3] = rs.getString("Released"); + cacheData[4] = rs.getString("Season"); + cacheData[5] = rs.getString("Episode"); + cacheData[6] = rs.getString("Runtime"); + cacheData[7] = rs.getString("Genre"); + cacheData[8] = rs.getString("Director"); + cacheData[9] = rs.getString("Writer"); + cacheData[10] = rs.getString("Actors"); + cacheData[11] = rs.getString("Plot"); + cacheData[12] = rs.getString("Language"); + cacheData[13] = rs.getString("Country"); + cacheData[14] = rs.getString("Awards"); + cacheData[15] = rs.getString("metascore"); + cacheData[16] = rs.getString("imdbRating"); + cacheData[17] = rs.getString("Type"); + cacheData[18] = rs.getString("BoxOffice"); + cacheData[19] = rs.getString("Website"); + cacheData[20] = rs.getString("Poster"); } - responseText.add(new Text(rs.getString(19) + "\n")); - im = new Image(new File(rs.getString(20)).toURI().toString()); - - stmt.close(); rs.close(); - - for (int i = 0; i < nameText.size(); i++) { - nameText.get(i).setFont(Font.font("System", FontWeight.BOLD, fontSize)); - responseText.get(i).setFont(Font.font("System", fontSize)); - } - - mainWindowController.getTextFlow().getChildren().remove(0, - mainWindowController.getTextFlow().getChildren().size()); - - for (int i = 0; i < nameText.size(); i++) { - mainWindowController.getTextFlow().getChildren().addAll(nameText.get(i), responseText.get(i)); - } - - try { - mainWindowController.getPosterImageView().setImage(im); - } catch (Exception e) { - mainWindowController.getPosterImageView().setImage(new Image("resources/icons/close_black_2048x2048.png")); - LOGGER.error(e); - } - + ps.close(); } catch (SQLException e) { LOGGER.error("Ups! an error occured!", e); } + + return cacheData; + } + + /** + * get all NOT cached entries + * @return a {@link ArrayList} of all NOT cached entries + */ + public ArrayList getAllNotCachedEntries() { + ArrayList notCachedEntries = new ArrayList<>(); + + try { + Statement stmt = connection.createStatement(); + ResultSet rs = stmt.executeQuery("SELECT * FROM films WHERE cached = 0"); + while (rs.next()) { + notCachedEntries.add(new FilmTabelDataType(rs.getString("streamUrl"), + rs.getString("title"), rs.getString("season"), rs.getString("episode"))); + } + stmt.close(); + rs.close(); + } catch (SQLException e) { + LOGGER.error("An error occured, while getting all NOT cached entries", e); + } + + LOGGER.info("There are {} entries not Chached!", notCachedEntries.size()); + return notCachedEntries; } /** @@ -635,14 +608,15 @@ public class DBController { * @return {@link Double} currentTime in ms */ public double getCurrentTime(String streamUrl) { - LOGGER.info("currentTime: " + streamUrl); + LOGGER.info("get currentTime for " + streamUrl); double currentTime = 0; try { - Statement stmt = connection.createStatement(); - ResultSet rs = stmt.executeQuery("SELECT * FROM films WHERE streamUrl = \"" + streamUrl + "\";"); + PreparedStatement ps = connection.prepareStatement("SELECT * FROM films WHERE streamUrl = ?"); + ps.setString(1, streamUrl); + ResultSet rs = ps.executeQuery(); currentTime = rs.getDouble("currentTime"); rs.close(); - stmt.close(); + ps.close(); } catch (Exception e) { LOGGER.error("Ups! error while getting the current time!", e); } @@ -656,133 +630,116 @@ public class DBController { * @param currentTime currentTime in ms of the film */ public void setCurrentTime(String streamUrl, double currentTime) { - LOGGER.info("currentTime: " + streamUrl); + LOGGER.info("set currentTime = " + currentTime + " for " + streamUrl); try { - Statement stmt = connection.createStatement(); - stmt.executeUpdate("UPDATE films SET currentTime=" + currentTime + " WHERE streamUrl=\"" + streamUrl + "\";"); + PreparedStatement ps = connection.prepareStatement("UPDATE films SET currentTime = ? WHERE streamUrl = ?"); + ps.setDouble(1, currentTime); + ps.setString(2, streamUrl); + ps.executeUpdate(); connection.commit(); - stmt.close(); + ps.close(); } catch (SQLException e) { - LOGGER.error("Ups! an error occured!", e); + LOGGER.error("Ups! error while updateing the current time!", e); } } /** - * get the next episode of a - * @param title URL of the film - * @param nextEp number of the next episode + * return the cache data for all episodes of a series season + * @param title the title of the series + * @param season the season you want + * @return a ArrayList of cache data (String Array [21]) + */ + public ArrayList getEpisodes(String title, int season) { + ArrayList episodePosters =new ArrayList<>(); + try { + // try to get a all episode of one season + PreparedStatement ps = connection.prepareStatement("SELECT streamUrl, episode FROM films WHERE title = ? AND season = ?"); + ps.setString(1, title); + ps.setString(2, Integer.toString(season)); + ResultSet rs = ps.executeQuery(); + + // for each episode load cache data + while (rs.next()) { + String[] cacheData = readCache(rs.getString("streamUrl")); + String[] ret = {rs.getString("streamUrl"), rs.getString("episode"), cacheData[20]}; + episodePosters.add(ret); + } + } catch(Exception e) { + LOGGER.error("Ups! error while getting episodes of a season!", e); + } + + return episodePosters; + } + + /** + * get the next episode of a series + * @param title title of the film + * @param episode current episode + * @param season season of the current episode * @return {@link FilmTabelDataType} the next episode as object */ public FilmTabelDataType getNextEpisode(String title, int episode, int season) { FilmTabelDataType nextFilm = null; - ResultSet rs; - int nextEpisode = 3000; try { - Statement stmt = connection.createStatement(); + // try to get a new episode of the current season + PreparedStatement ps = connection.prepareStatement("SELECT * FROM films WHERE title = ? AND season = ? AND episode = ?"); + ps.setString(1, title); + ps.setString(2, Integer.toString(season)); + ps.setString(3, Integer.toString(episode + 1)); + ResultSet rs = ps.executeQuery(); - rs = stmt.executeQuery("SELECT * FROM films WHERE title = \"" + title + "\" AND season = \"" + season + "\";"); - while(rs.next()) { - int rsEpisode = Integer.parseInt(rs.getString("episode")); - if (rsEpisode > episode && rsEpisode < nextEpisode) { - // fitting episode found in current season, if rsEpisode < nextEpisode -> nextEpisode = rsEpisode - nextEpisode = rsEpisode; - System.out.println("next episode is: " + nextEpisode); - // favorite image is black - nextFilm = new FilmTabelDataType(rs.getString("streamUrl"), rs.getString("title"), - rs.getString("season"), rs.getString("episode"), rs.getBoolean("favorite"), - rs.getBoolean("cached"), new ImageView(favorite_black)); - } + /* if that fails get the next season and try to get a episode there, + * we need to test only for the next season, first episode, + * any other entry would not exist in the database + */ + if(!rs.next()) { + ps.setString(1, title); + ps.setString(2, Integer.toString(season + 1)); + ps.setString(3, Integer.toString(1)); + rs = ps.executeQuery(); + if(!rs.next()) return nextFilm; // if we haven't found anything return an empty object } - if (nextFilm == null) { - int nextSeason = 3000; - System.out.println("searching next season"); - rs = stmt.executeQuery("SELECT * FROM films WHERE title = \"" + title + "\";"); - while(rs.next()) { - int rsSeason = Integer.parseInt(rs.getString("season")); - if (rsSeason > season && rsSeason < nextSeason) { - nextSeason = rsSeason; - } - } - - if (nextSeason != 3000) { - System.out.println("next season is: " + nextSeason); - rs = stmt.executeQuery("SELECT * FROM films WHERE title = \"" + title + "\" AND season = \"" + season + "\";"); - while(rs.next()) { - int rsEpisode = Integer.parseInt(rs.getString("episode")); - if (rsEpisode > episode && rsEpisode < nextEpisode) { - // fitting episode found in current season, if rsEpisode < nextEpisode -> nextEpisode = rsEpisode - nextEpisode = rsEpisode; - System.out.println("next episode is: " + nextEpisode); - // favorite image is black - nextFilm = new FilmTabelDataType(rs.getString("streamUrl"), rs.getString("title"), - rs.getString("season"), rs.getString("episode"), rs.getBoolean("favorite"), - rs.getBoolean("cached"), new ImageView(favorite_black)); - } - } - } - } + // at this point we have found the correct episode + nextFilm = new FilmTabelDataType(rs.getString("streamUrl"), rs.getString("title"), + rs.getString("season"), rs.getString("episode")); + rs.close(); - stmt.close(); + ps.close(); } catch (Exception e) { LOGGER.error("Ups! error while getting next episode!", e); } return nextFilm; } - /** TODO check if we relay need to separate between favorites and none favorites + /** TODO rework, we should save the next episode in the root entry * get the last watched episode - * @param title the title of the series - * @return the last watched episode as {@link FilmTabelDataType} object + * @param streamURL URL of the stream + * @return the last watched episodes URL */ - public FilmTabelDataType getLastWatchedEpisode(String title) { - LOGGER.info("last watched episode of: " + title); - FilmTabelDataType nextFilm = null; + public String getLastWatchedEpisode(String streamURL) { + LOGGER.info("last watched episode in: " + streamURL); + String lastEp = null; double lastCurrentTime = 0; try { - Statement stmt = connection.createStatement(); - ResultSet rs = stmt.executeQuery("SELECT * FROM films WHERE title = \"" + title + "\";"); + PreparedStatement ps = connection.prepareStatement("SELECT * FROM films WHERE streamUrl LIKE ?"); + ps.setString(1, streamURL + "/%"); + ResultSet rs = ps.executeQuery(); while (rs.next()) { - if (rs.getBoolean("favorite") == true) { - nextFilm = new FilmTabelDataType(rs.getString("streamUrl"), - rs.getString("title"), rs.getString("season"), rs.getString("episode") ,rs.getBoolean("favorite"), - rs.getBoolean("cached"), new ImageView(favorite_black)); - } else { - nextFilm = new FilmTabelDataType(rs.getString("streamUrl"), - rs.getString("title"), rs.getString("season"), rs.getString("episode"), rs.getBoolean("favorite"), - rs.getBoolean("cached"), new ImageView(favorite_border_black)); - } - if (rs.getDouble("currentTime") > lastCurrentTime) { - lastCurrentTime = rs.getDouble("currentTime"); - if (rs.getBoolean("favorite") == true) { - nextFilm = new FilmTabelDataType(rs.getString("streamUrl"), - rs.getString("title"), rs.getString("season"), rs.getString("episode") ,rs.getBoolean("favorite"), - rs.getBoolean("cached"), new ImageView(favorite_black)); - } else { - nextFilm = new FilmTabelDataType(rs.getString("streamUrl"), - rs.getString("title"), rs.getString("season"), rs.getString("episode"), rs.getBoolean("favorite"), - rs.getBoolean("cached"), new ImageView(favorite_border_black)); - } - break; + // get the first episode where currentTime != 0 + if (rs.getDouble("currentTime") > lastCurrentTime || lastEp == null) { + lastEp = rs.getString("streamUrl"); } } rs.close(); - stmt.close(); + ps.close(); } catch (Exception e) { LOGGER.error("Ups! error while getting the last watched episode!", e); } - - return nextFilm; - } - - // removes the ending - private String cutOffEnd(String str) { - if (str == null) return null; - int pos = str.lastIndexOf("."); - if (pos == -1) return str; - return str.substring(0, pos); + + return lastEp; } /** diff --git a/src/main/java/kellerkinder/HomeFlix/controller/OMDbAPIController.java b/src/main/java/kellerkinder/HomeFlix/controller/OMDbAPIController.java index bc4db09..54b0852 100644 --- a/src/main/java/kellerkinder/HomeFlix/controller/OMDbAPIController.java +++ b/src/main/java/kellerkinder/HomeFlix/controller/OMDbAPIController.java @@ -1,7 +1,7 @@ /** * Project-HomeFlix * - * Copyright 2018 <@Seil0> + * Copyright 2018-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 @@ -19,6 +19,7 @@ * MA 02110-1301, USA. * */ + package kellerkinder.HomeFlix.controller; import java.awt.image.BufferedImage; @@ -36,135 +37,188 @@ import com.eclipsesource.json.Json; import com.eclipsesource.json.JsonObject; import com.eclipsesource.json.JsonValue; -import javafx.application.Platform; -import kellerkinder.HomeFlix.application.Main; -import kellerkinder.HomeFlix.application.MainWindowController; +import kellerkinder.HomeFlix.datatypes.FilmTabelDataType; +import kellerkinder.HomeFlix.datatypes.OMDbAPIResponseDataType; public class OMDbAPIController implements Runnable { - private MainWindowController mainWindowController; private DBController dbController; - private Main main; - private String[] responseString = new String[20]; + private FilmTabelDataType currentTableFilm; + private String omdbAPIKey; private String URL = "https://www.omdbapi.com/?apikey="; - private static final Logger LOGGER = LogManager.getLogger(MainWindowController.class.getName()); + private static final Logger LOGGER = LogManager.getLogger(OMDbAPIController.class.getName()); /** * constructor for the OMDbAPIController - * @param mainWindowController the MainWindowController object - * @param dbController the DBController object * @param main the Main object + * @param dbController the DBController object + * @param currentTableFilm the current film object + * @param omdbAPIKey the omdbAPI key */ - public OMDbAPIController(MainWindowController mainWindowController, DBController dbController, Main main){ - this.mainWindowController = mainWindowController; - this.dbController = dbController; - this.main = main; - - + public OMDbAPIController(FilmTabelDataType currentTableFilm) { + this.currentTableFilm = currentTableFilm; + omdbAPIKey = XMLController.getOmdbAPIKey(); + dbController = DBController.getInstance(); } @Override public void run() { - String output = null; - String posterPath = null; + LOGGER.info("Querying omdbAPI ..."); + JsonObject object; - // get information by title - try { - URL apiUrl = new URL(URL + mainWindowController.getOmdbAPIKey() + "&t=" - + mainWindowController.getCurrentTitle().replace(" ", "%20")); - BufferedReader ina = new BufferedReader(new InputStreamReader(apiUrl.openStream())); - output = ina.readLine(); - ina.close(); - System.out.println(apiUrl); - LOGGER.info("response from '" + URL + "&t=" + mainWindowController.getCurrentTitle() + "' was:" + output); - } catch (IOException e) { - LOGGER.error("error while making api request or reading response"); - LOGGER.error("response from '" + URL + "&t=" + mainWindowController.getCurrentTitle() + "' was:" + output, e); + if (currentTableFilm.getSeason() != null && Integer.parseInt(currentTableFilm.getSeason() + 0) > 0) { + object = getByTitle(currentTableFilm.getTitle(), true); + } else { + object = getByTitle(currentTableFilm.getTitle(), false); + } + + if (object == null) { + LOGGER.error("Fatal error while querying omdbAPI!"); return; } - JsonObject object = Json.parse(output).asObject(); - - if (object.getString("Error", "").equals("Movie not found!")) { - // if the movie was not found try to search it - LOGGER.warn("Movie was not found at first try, searching again!"); - /** TODO - * split the name intelligent as it may contain the film title - * search for English name - * use tmdb - */ - try { - URL apiUrl = new URL(URL + mainWindowController.getOmdbAPIKey() + "&s=" - + mainWindowController.getCurrentTitle().replace(" ", "%20")); - BufferedReader ina = new BufferedReader(new InputStreamReader(apiUrl.openStream())); - output = ina.readLine(); - ina.close(); - LOGGER.info("response from '" + URL + "&s=" + mainWindowController.getCurrentTitle() + "' was:" + output); - } catch (Exception e) { - LOGGER.error("error while making api request or reading response"); - LOGGER.error("response from '" + URL + "&s=" + mainWindowController.getCurrentTitle() + "' was:" + output, e); - return; - } - - JsonObject searchObject = Json.parse(output).asObject(); - if (searchObject.getString("Response", "").equals("True")) { - for (JsonValue movie : searchObject.get("Search").asArray()) { - // get first entry from the array and set object = movie - object = (JsonObject) movie; - System.out.println(movie.toString()); - break; - + // if the answer contains "not found!" try to search by title + if (object.getString("Error", "").contains("not found!")) { + String title = searchByTitle(currentTableFilm.getTitle()); + if (title.length() > 0) { + // we have at least one answer, get info by search title now + if (currentTableFilm.getSeason() != null && Integer.parseInt(currentTableFilm.getSeason() + 0) > 0) { + object = getByTitle(title, true); + } else { + object = getByTitle(title, false); } - System.out.println(object.getString("Title", "")); } else { - LOGGER.warn("Movie not found! Not adding cache!"); + // add default poster and cache + LOGGER.warn("Adding default poster and cache entries for \"{}\"!", currentTableFilm.getTitle()); + OMDbAPIResponseDataType omdbResponse = new OMDbAPIResponseDataType(); + omdbResponse.setTitle(currentTableFilm.getTitle()); + omdbResponse.setSeason(currentTableFilm.getSeason()); + omdbResponse.setEpisode(currentTableFilm.getEpisode()); + + synchronized (this) { + // adding to cache + dbController.addCache(currentTableFilm.getStreamUrl(), omdbResponse); + dbController.setCached(currentTableFilm.getStreamUrl()); + } + return; } } - // add the response to the responseString[] - responseString[0] = object.getString("Title", ""); - responseString[1] = object.getString("Year", ""); - responseString[2] = object.getString("Rated", ""); - responseString[3] = object.getString("Released", ""); - responseString[4] = object.getString("Runtime", ""); - responseString[5] = object.getString("Genre", ""); - responseString[6] = object.getString("Director", ""); - responseString[7] = object.getString("Writer", ""); - responseString[8] = object.getString("Actors", ""); - responseString[9] = object.getString("Plot", ""); - responseString[10] = object.getString("Language", ""); - responseString[11] = object.getString("Country", ""); - responseString[12] = object.getString("Awards", ""); - responseString[13] = object.getString("Metascore", ""); - responseString[14] = object.getString("imdbRating", ""); - responseString[15] = object.getString("Type", ""); - responseString[16] = object.getString("imdbVotes", ""); - responseString[17] = object.getString("imdbID", ""); - responseString[18] = object.getString("Poster", ""); - responseString[19] = object.getString("Response", ""); - - //resize the image to fit in the posterImageView and add it to the cache - try { - BufferedImage originalImage = ImageIO.read(new URL(responseString[18])); //change path to where file is located - posterPath = main.getPosterCache() + "/" + mainWindowController.getCurrentTitle() + ".png"; - ImageIO.write(originalImage, "png", new File(posterPath)); - LOGGER.info("adding poster to cache: "+posterPath); + OMDbAPIResponseDataType omdbResponse = new OMDbAPIResponseDataType(); + omdbResponse.setTitle(object.getString("Title", "")); + omdbResponse.setYear(object.getString("Year", "")); + omdbResponse.setRated(object.getString("Rated", "")); + omdbResponse.setReleased(object.getString("Release", "")); + omdbResponse.setSeason(object.getString("Season", "")); + omdbResponse.setEpisode(object.getString("Episode", "")); + omdbResponse.setRuntime(object.getString("Runtime", "")); + omdbResponse.setGenre(object.getString("Genre", "")); + omdbResponse.setDirector(object.getString("Director", "")); + omdbResponse.setWriter(object.getString("Writer", "")); + omdbResponse.setActors(object.getString("Actors", "")); + omdbResponse.setPlot(object.getString("Plot", "")); + omdbResponse.setLanguage(object.getString("Language", "")); + omdbResponse.setCountry(object.getString("Country", "")); + omdbResponse.setAwards(object.getString("Awards", "")); + omdbResponse.setMetascore(object.getString("Metascore", "")); + omdbResponse.setImdbRating(object.getString("imdbRating", "")); + omdbResponse.setImdbVotes(object.getString("imdbVotes", "")); + omdbResponse.setImdbID(object.getString("imdbID", "")); + omdbResponse.setType(object.getString("Type", "")); + omdbResponse.setDvd(object.getString("DVD", "")); + omdbResponse.setBoxOffice(object.getString("BoxOffice", "")); + omdbResponse.setProduction(object.getString("Production", "")); + omdbResponse.setWebsite(object.getString("Website", "")); + omdbResponse.setResponse(object.getString("Response", "")); + + // if a poster exist try resizing it to fit in the posterImageView and add it to the cache, else use the default + try { + BufferedImage originalImage = ImageIO.read(new URL(object.getString("Poster", ""))); + // change path to where file is located + omdbResponse.setPoster(XMLController.getPosterCache() + "/" + omdbResponse.getTitle() + ".png"); + ImageIO.write(originalImage, "png", new File(omdbResponse.getPoster())); + LOGGER.info("adding poster to cache: " + omdbResponse.getPoster()); } catch (Exception e) { - LOGGER.error(e); + LOGGER.warn("could not load poster, seting null -> using default"); } - - // adding strings to the cache - dbController.addCache(mainWindowController.getCurrentStreamUrl(), responseString[0], responseString[1], - responseString[2], responseString[3], responseString[4], responseString[5], responseString[6], - responseString[7], responseString[8], responseString[9], responseString[10], responseString[11], - responseString[12], responseString[13], responseString[14], responseString[15], responseString[16], - responseString[17], posterPath, responseString[19]); - dbController.setCached(mainWindowController.getCurrentStreamUrl()); - // load data to the MainWindowController - Platform.runLater(() -> { - dbController.readCache(mainWindowController.getCurrentStreamUrl()); - }); + synchronized (this) { + // adding to cache + dbController.addCache(currentTableFilm.getStreamUrl(), omdbResponse); + dbController.setCached(currentTableFilm.getStreamUrl()); + } + + return; + } + + /** + * get a movie/series by its title + * @param title of the movie/series + * @return a jsonObject of the API answer + */ + private JsonObject getByTitle(String title, boolean useEpisode) { + String output = null; + URL apiUrl; + try { + if (useEpisode) { + apiUrl = new URL(URL + omdbAPIKey + "&t=" + + title.replace(" ", "%20") + + "&Season=" + currentTableFilm.getSeason() + + "&Episode=" + currentTableFilm.getEpisode() + + "&plot=full"); + } else { + apiUrl = new URL(URL + omdbAPIKey + "&t=" + + title.replace(" ", "%20") + "&plot=full"); + } + + BufferedReader ina = new BufferedReader(new InputStreamReader(apiUrl.openStream())); + output = ina.readLine(); + ina.close(); + LOGGER.info("response from '" + URL + "&t=" + title + "' was:" + output); + } catch (IOException e) { + LOGGER.error("error while making api request or reading response"); + LOGGER.error("response from '" + URL + "&t=" + title + "' was:" + output, e); + return null; + } + + return Json.parse(output).asObject(); + } + + /** + * search for a movie/series title + * @param title the movie/series title + * @return the correct title if found + */ + private String searchByTitle(String title) { + String output = null; + // if the movie was not found try to search it + LOGGER.warn("Movie was not found at first try, searching again!"); + /** + * TODO split the name intelligent as it may contain the film title search for + * English name use tmdb + */ + try { + URL apiUrl = new URL(URL + omdbAPIKey + "&s=" + title.replace(" ", "%20")); + BufferedReader ina = new BufferedReader(new InputStreamReader(apiUrl.openStream())); + output = ina.readLine(); + ina.close(); + LOGGER.info("response from '" + URL + "&s=" + title + "' was:" + output); + } catch (Exception e) { + LOGGER.error("error while making api request or reading response"); + LOGGER.error("response from '" + URL + "&s=" + title + "' was:" + output, e); + return ""; + } + + JsonObject searchObject = Json.parse(output).asObject(); + if (searchObject.getString("Response", "").equals("True")) { + for (JsonValue movie : searchObject.get("Search").asArray()) { + // get first entry from the array and set object = movie + return movie.asObject().getString("Title", ""); + } + } else { + LOGGER.warn("Movie \"{}\" not found!", title); + } + return ""; } } diff --git a/src/main/java/kellerkinder/HomeFlix/controller/SourcesController.java b/src/main/java/kellerkinder/HomeFlix/controller/SourcesController.java new file mode 100644 index 0000000..76dc8fc --- /dev/null +++ b/src/main/java/kellerkinder/HomeFlix/controller/SourcesController.java @@ -0,0 +1,179 @@ +/** + * 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.controller; + +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.List; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import com.eclipsesource.json.Json; +import com.eclipsesource.json.JsonArray; +import com.eclipsesource.json.JsonObject; +import com.eclipsesource.json.JsonValue; + +import kellerkinder.HomeFlix.datatypes.DatabaseDataType; + +public class SourcesController { + + private List sourceStreams = new ArrayList(); + private static final Logger LOGGER = LogManager.getLogger(SourcesController.class.getName()); + + public SourcesController() { + // Auto-generated constructor stub + } + + /** + * load all local and streaming sources and add them to a list + */ + public List loadSources() { + + try { + // create a JsonArray, containing all sources, add each source to the mwc, get all films from it + JsonArray sources = Json.parse(new FileReader(XMLController.getDirHomeFlix() + "/sources.json")).asArray(); + for (JsonValue source : sources) { + String path = source.asObject().getString("path", ""); + String mode = source.asObject().getString("mode", ""); + + if (mode.equals("local")) + addLocalSource(path); + if (mode.equals("stream")) + addStreamSource(path); + } + } catch (Exception e) { + e.printStackTrace(); + } + + LOGGER.info("films in directory: " + sourceStreams.size()); + + return sourceStreams; + } + /** + * for each file in source path + * check whether it's valid file or a directory (series) + * if it's valid or a series add it to the sourceStreams + * @param a local source path (a directory) + */ + private void addLocalSource(String path) { + + int oldSize = sourceStreams.size(); + for (File file : new File(path).listFiles()) { + // if it's valid file add it to the sourceStreams + if (isValidFile(file)) { + sourceStreams.add(new DatabaseDataType(file.getPath(), cutOffEnd(file.getName()), "", "", 0, 0.0)); + } else if(isValidSeriesRoot(file)) { + // get all directories (series), root and season must be directories + sourceStreams.add(new DatabaseDataType(file.getPath(), file.getName(), "0", "0", 0, 0.0)); // add the series root node + int sn = 1; + for (File season : file.listFiles()) { + if (season.isDirectory()) { + int ep = 1; + for (File episode : season.listFiles()) { + // check whether the episode is a valid file + if (isValidFile(episode)) { + sourceStreams.add(new DatabaseDataType(episode.getPath(), cutOffEnd(file.getName()), + Integer.toString(sn), Integer.toString(ep), 0, 0.0)); + ep++; + } + } + sn++; + } + } + } + } + + LOGGER.info("added " + (sourceStreams.size() - oldSize) + " local files from: " + path); + } + + private void addStreamSource(String path) { + // getting all entries from the streaming lists + try { + JsonObject object = Json.parse(new FileReader(path)).asObject(); + JsonArray items = object.get("entries").asArray(); + for (JsonValue item : items) { + sourceStreams.add(new DatabaseDataType(item.asObject().getString("streamUrl", ""), + item.asObject().getString("title", ""), + item.asObject().getString("season", ""), + item.asObject().getString("episode", ""), 0, 0.0)); + } + LOGGER.info("added " + items.size() +" stream entries from: " + path); + } catch (IOException e) { + LOGGER.error(e); + } + + + } + + /** + * check if a file is a valid file (is video and file) + * @param file the actual file + * @return true if it's valid, else false + */ + private boolean isValidFile(File file) { + try { + String mimeType = Files.probeContentType(file.toPath()); + if (file.isFile() && mimeType != null && (mimeType.startsWith("video") || mimeType.contains("matroska"))) { + return true; + } else { +// System.out.println(file.getPath() + " mime type: " + mimeType); + } + } catch (IOException e) { + LOGGER.error(e); + } + + return false; + } + + /** + * + * @param file the root file you want to check + * @return true if it's a valid series root, else false + */ + private boolean isValidSeriesRoot(File file) { + if(file.isDirectory()) { + for (File season : file.listFiles()) { + if (season.isDirectory()) { + return true; + } else { + return false; // the root directory not only folders + } + } + } + + return false; + } + + // removes the ending + private String cutOffEnd(String str) { + if (str == null) return null; + int pos = str.lastIndexOf("."); + if (pos == -1) return str; + return str.substring(0, pos); + } + +} diff --git a/src/main/java/kellerkinder/HomeFlix/controller/UpdateController.java b/src/main/java/kellerkinder/HomeFlix/controller/UpdateController.java index efa3ffe..7e1cf2c 100644 --- a/src/main/java/kellerkinder/HomeFlix/controller/UpdateController.java +++ b/src/main/java/kellerkinder/HomeFlix/controller/UpdateController.java @@ -1,7 +1,7 @@ /** * Project-HomeFlix * - * Copyright 2018 <@Seil0> + * Copyright 2018-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 @@ -18,6 +18,7 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ + package kellerkinder.HomeFlix.controller; import java.io.BufferedReader; @@ -39,127 +40,96 @@ import com.eclipsesource.json.JsonArray; import com.eclipsesource.json.JsonObject; import com.eclipsesource.json.JsonValue; -import javafx.application.Platform; -import kellerkinder.HomeFlix.application.MainWindowController; +import kellerkinder.HomeFlix.application.Main; -public class UpdateController implements Runnable { +public class UpdateController { - private MainWindowController mainWindowController; - private String buildNumber; - private String apiOutput; - private String updateBuildNumber; // tag_name from Github -// private String updateName; -// private String updateChanges; - private String browserDownloadUrl; // update download link - private String githubApiRelease = "https://api.github.com/repos/Seil0/Project-HomeFlix/releases/latest"; - private String githubApiBeta = "https://api.github.com/repos/Seil0/Project-HomeFlix/releases"; + private static int updateBuildNumber; // tag_name from gitea + @SuppressWarnings("unused") + private static String updateName; + @SuppressWarnings("unused") + private static String updateChanges; + private static String browserDownloadUrl; // update download link + private static final String giteaApiRelease = "https://git.mosad.xyz/api/v1/repos/Seil0/Project-HomeFlix/releases"; - private URL githubApiUrl; - private boolean useBeta; private static final Logger LOGGER = LogManager.getLogger(UpdateController.class.getName()); /** * updater for Project HomeFlix based on cemu_UIs, checks for Updates and download it - * @param mwc the MainWindowController object - * @param buildNumber the buildNumber of the used HomeFlix version - * @param useBeta if the updater should query the beta channel */ - public UpdateController(MainWindowController mwc, String buildNumber, boolean useBeta) { - mainWindowController = mwc; - this.buildNumber = buildNumber; - this.useBeta = useBeta; + public UpdateController() { + } - @Override - public void run() { - LOGGER.info("beta:" + useBeta + "; checking for updates ..."); - Platform.runLater(() -> { - mainWindowController.getUpdateBtn().setText(mainWindowController.getBundle().getString("updateBtnChecking")); - }); + public static void update() { + if (browserDownloadUrl != null) { + LOGGER.info("download link: " + browserDownloadUrl); + + Runnable run = () -> { + try { + // open new HTTP connection, ProgressMonitorInputStream for downloading the data + // FIXME the download progress dialog is not showing! + HttpURLConnection connection = (HttpURLConnection) new URL(browserDownloadUrl).openConnection(); + ProgressMonitorInputStream pmis = new ProgressMonitorInputStream(null, "Downloading...", connection.getInputStream()); + ProgressMonitor pm = pmis.getProgressMonitor(); + pm.setMillisToDecideToPopup(0); + pm.setMillisToPopup(0); + pm.setMinimum(0);// set beginning of the progress bar to 0 + pm.setMaximum(connection.getContentLength());// set the end to the file length + FileUtils.copyInputStreamToFile(pmis, new File("ProjectHomeFlix_update.jar")); // download update + LOGGER.info("update download successful, restarting ..."); + org.apache.commons.io.FileUtils.copyFile(new File("ProjectHomeFlix_update.jar"), new File("ProjectHomeFlix.jar")); + org.apache.commons.io.FileUtils.deleteQuietly(new File("ProjectHomeFlix_update.jar")); // delete update + new ProcessBuilder("java", "-jar", "ProjectHomeFlix.jar").start(); // start the new application + System.exit(0); // close the current application + } catch (IOException e) { + LOGGER.info("could not download update files", e); + } + }; + + Thread t = new Thread(run); + t.start(); + + } + } + + + public static boolean isUpdateAvailable() { + LOGGER.info("beta:" + XMLController.isUseBeta() + "; checking for updates ..."); + String apiOutput = ""; try { - - if (useBeta) { - githubApiUrl = new URL(githubApiBeta); - } else { - githubApiUrl = new URL(githubApiRelease); - } - - // URL githubApiUrl = new URL(githubApiRelease); - BufferedReader ina = new BufferedReader(new InputStreamReader(githubApiUrl.openStream())); + URL giteaApiUrl = new URL(giteaApiRelease); + + BufferedReader ina = new BufferedReader(new InputStreamReader(giteaApiUrl.openStream())); apiOutput = ina.readLine(); ina.close(); - } catch (IOException e) { - Platform.runLater(() -> { + } catch (Exception e) { LOGGER.error("could not check update version", e); - }); } - if (useBeta) { - JsonArray objectArray = Json.parse("{\"items\": " + apiOutput + "}").asObject().get("items").asArray(); - JsonValue object = objectArray.get(0); - JsonArray objectAssets = object.asObject().get("assets").asArray(); - - updateBuildNumber = object.asObject().getString("tag_name", ""); -// updateName = object.asObject().getString("name", ""); -// updateChanges = object.asObject().getString("body", ""); - - for (JsonValue asset : objectAssets) { - browserDownloadUrl = asset.asObject().getString("browser_download_url", ""); - } - - } else { - JsonObject object = Json.parse(apiOutput).asObject(); - JsonArray objectAssets = Json.parse(apiOutput).asObject().get("assets").asArray(); - - updateBuildNumber = object.getString("tag_name", ""); -// updateName = object.getString("name", ""); -// updateChanges = object.getString("body", ""); - for (JsonValue asset : objectAssets) { - browserDownloadUrl = asset.asObject().getString("browser_download_url", ""); - + JsonArray objectArray = Json.parse("{\"items\": " + apiOutput + "}").asObject().get("items").asArray(); + JsonValue object = objectArray.get(0).asObject(); // set to the latest release as default + JsonObject objectAsset = object.asObject().get("assets").asArray().get(0).asObject(); + + // TODO rework stream() + for(JsonValue objectIt : objectArray) { + if(objectIt.asObject().getBoolean("prerelease", false) == XMLController.isUseBeta()) { + // we found the needed release either beta or not + object = objectIt; + objectAsset = objectIt.asObject().get("assets").asArray().get(0).asObject(); + break; } } - LOGGER.info("Build: " + buildNumber + ", Update: " + updateBuildNumber); - - // Compares the program BuildNumber with the current BuildNumber if program - // BuildNumber < current BuildNumber then perform a update - int iversion = Integer.parseInt(buildNumber); - int iaktVersion = Integer.parseInt(updateBuildNumber.replace(".", "")); - - if (iversion >= iaktVersion) { - Platform.runLater(() -> { - mainWindowController.getUpdateBtn().setText(mainWindowController.getBundle().getString("updateBtnNoUpdateAvailable")); - }); - LOGGER.info("no update available"); - } else { - Platform.runLater(() -> { - mainWindowController.getUpdateBtn().setText(mainWindowController.getBundle().getString("updateBtnUpdateAvailable")); - }); - LOGGER.info("update available"); - LOGGER.info("download link: " + browserDownloadUrl); - try { - // open new Http connection, ProgressMonitorInputStream for downloading the data - HttpURLConnection connection = (HttpURLConnection) new URL(browserDownloadUrl).openConnection(); - ProgressMonitorInputStream pmis = new ProgressMonitorInputStream(null, "Downloading...", connection.getInputStream()); - ProgressMonitor pm = pmis.getProgressMonitor(); - pm.setMillisToDecideToPopup(0); - pm.setMillisToPopup(0); - pm.setMinimum(0);// set beginning of the progress bar to 0 - pm.setMaximum(connection.getContentLength());// set the end to the file length - FileUtils.copyInputStreamToFile(pmis, new File("ProjectHomeFlix_update.jar")); // download update - org.apache.commons.io.FileUtils.copyFile(new File("ProjectHomeFlix_update.jar"), new File("ProjectHomeFlix.jar")); - org.apache.commons.io.FileUtils.deleteQuietly(new File("ProjectHomeFlix_update.jar")); // delete update - new ProcessBuilder("java", "-jar", "ProjectHomeFlix.jar").start(); // start the new application - System.exit(0); // close the current application - } catch (IOException e) { - Platform.runLater(() -> { - LOGGER.info("could not download update files", e); - }); - } - } - + updateBuildNumber = Integer.parseInt(object.asObject().getString("tag_name", "")); + updateName = object.asObject().getString("name", ""); + updateChanges = object.asObject().getString("body", ""); + browserDownloadUrl = objectAsset.getString("browser_download_url", ""); + + LOGGER.info("Build: " + Main.buildNumber + ", Update: " + updateBuildNumber); + + return Integer.parseInt(Main.buildNumber) < updateBuildNumber; + } - } diff --git a/src/main/java/kellerkinder/HomeFlix/controller/XMLController.java b/src/main/java/kellerkinder/HomeFlix/controller/XMLController.java new file mode 100644 index 0000000..098f6db --- /dev/null +++ b/src/main/java/kellerkinder/HomeFlix/controller/XMLController.java @@ -0,0 +1,306 @@ +/** + * 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.controller; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.util.Locale; +import java.util.Properties; +import java.util.ResourceBundle; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import com.eclipsesource.json.Json; +import com.eclipsesource.json.JsonObject; + +public class XMLController { + + private static final String userHome = System.getProperty("user.home"); + private static final String userName = System.getProperty("user.name"); + private static final String osName = System.getProperty("os.name"); + private static final String osArch = System.getProperty("os.arch"); + private static final String osVers = System.getProperty("os.version"); + private static final String javaVers = System.getProperty("java.version"); + private static final String javaVend = System.getProperty("java.vendor"); + private static final String sysLocal = System.getProperty("user.language") + "_" + System.getProperty("user.country"); + private static String dirHomeFlixPath; + private static File dirHomeFlix; + private static File configFile; + private static File posterCache; + + // user settings + private static String color = "ee3523"; + private static String usrLocal = sysLocal; + private static boolean autoUpdate = false; + private static boolean useBeta = false; + private static boolean autoplay = false; + private static double fontSize = 17; + private static ResourceBundle localBundle = ResourceBundle.getBundle("locals.HomeFlix-Local", Locale.US); + + // api settings + private static String omdbAPIKey; + + private static final Logger LOGGER = LogManager.getLogger(XMLController.class.getName()); + private Properties props = new Properties(); + + /** + * prepare the start up, this is the first thing call by main + */ + public XMLController() { + + if (osName.contains("Windows")) { + dirHomeFlixPath = userHome + "/Documents/HomeFlix"; + } else { + dirHomeFlixPath = userHome + "/HomeFlix"; + } + + // set the concrete files + dirHomeFlix = new File(dirHomeFlixPath); + configFile = new File(dirHomeFlix + "/config.xml"); + posterCache = new File(dirHomeFlix + "/posterCache"); + + } + + /** + * save the configuration to the config.xml file + */ + public void saveSettings() { + LOGGER.info("saving settings ..."); + try { + props.setProperty("color", color); + props.setProperty("autoUpdate", String.valueOf(autoUpdate)); + props.setProperty("useBeta", String.valueOf(useBeta)); + props.setProperty("autoplay", String.valueOf(autoplay)); + props.setProperty("size", Double.toString(fontSize)); + props.setProperty("local", usrLocal); + + OutputStream outputStream = new FileOutputStream(getConfigFile()); // new output-stream + props.storeToXML(outputStream, "Project HomeFlix settings"); // write new .xml + outputStream.close(); + } catch (IOException e) { + LOGGER.error("An error occurred while saving the settings!", e); + } + } + + /** + * load the configuration from the config.xml file + * and try to load the API keys from apiKeys.json + */ + public void loadSettings() { + LOGGER.info("loading settings ..."); + + try { + InputStream inputStream = new FileInputStream(getConfigFile()); + props.loadFromXML(inputStream); // new input-stream from .xml + + try { + setColor(props.getProperty("color")); + } catch (Exception e) { + LOGGER.error("cloud not load color", e); + setColor("00a8cc"); + } + + try { + setFontSize(Double.parseDouble(props.getProperty("size"))); + } catch (Exception e) { + LOGGER.error("cloud not load fontsize", e); + setFontSize(17.0); + } + + try { + setAutoUpdate(Boolean.parseBoolean(props.getProperty("autoUpdate"))); + } catch (Exception e) { + LOGGER.error("cloud not load autoUpdate", e); + setAutoUpdate(false); + } + + try { + setUseBeta(Boolean.parseBoolean(props.getProperty("useBeta"))); + } catch (Exception e) { + LOGGER.error("cloud not load autoUpdate", e); + setUseBeta(false); + } + + try { + setAutoplay(Boolean.parseBoolean(props.getProperty("autoplay"))); + } catch (Exception e) { + LOGGER.error("cloud not load autoplay", e); + setAutoplay(false); + } + + try { + setUsrLocal(props.getProperty("local")); + } catch (Exception e) { + LOGGER.error("cloud not load local", e); + setUsrLocal(System.getProperty("user.language") + "_" + System.getProperty("user.country")); + } + + inputStream.close(); + } catch (IOException e) { + LOGGER.error("An error occurred while loading the settings!", e); + } + + // try loading the omdbAPI key + try { + InputStream in = getClass().getClassLoader().getResourceAsStream("apiKeys.json"); + if (in != null) { + BufferedReader reader = new BufferedReader(new InputStreamReader(in)); + JsonObject apiKeys = Json.parse(reader).asObject(); + setOmdbAPIKey(apiKeys.getString("omdbAPIKey", "")); + reader.close(); + in.close(); + } else { + LOGGER.warn("Cloud not load apiKeys.json. No such file"); + } + } catch (Exception e) { + LOGGER.error("Cloud not load the omdbAPI key. Please contact the developer!", e); + } + } + + // getters for application variables + + public static String getUserHome() { + return userHome; + } + + public static String getUserName() { + return userName; + } + + public static String getOsName() { + return osName; + } + + public static String getOsArch() { + return osArch; + } + + public static String getOsVers() { + return osVers; + } + + public static String getJavaVers() { + return javaVers; + } + + public static String getJavaVend() { + return javaVend; + } + + public static String getSysLocal() { + return sysLocal; + } + + public static String getDirHomeFlixPath() { + return dirHomeFlixPath; + } + + + public static File getDirHomeFlix() { + return dirHomeFlix; + } + + public static File getConfigFile() { + return configFile; + } + + public static File getPosterCache() { + return posterCache; + } + + // getters for user settings + + public static String getColor() { + return color; + } + + public static void setColor(String color) { + XMLController.color = color; + } + + public static String getUsrLocal() { + return usrLocal; + } + + public static void setUsrLocal(String usrLocal) { + XMLController.usrLocal = usrLocal; + } + + public static boolean isAutoUpdate() { + return autoUpdate; + } + + public static void setAutoUpdate(boolean autoUpdate) { + XMLController.autoUpdate = autoUpdate; + } + + public static boolean isUseBeta() { + return useBeta; + } + + public static void setUseBeta(boolean useBeta) { + XMLController.useBeta = useBeta; + } + + public static boolean isAutoplay() { + return autoplay; + } + + public static void setAutoplay(boolean autoplay) { + XMLController.autoplay = autoplay; + } + + public static double getFontSize() { + return fontSize; + } + + public static void setFontSize(double fontSize) { + XMLController.fontSize = fontSize; + } + + public static ResourceBundle getLocalBundle() { + return localBundle; + } + + public static void setLocalBundle(ResourceBundle localBundle) { + XMLController.localBundle = localBundle; + } + + + // getters for APIs + + public static String getOmdbAPIKey() { + return omdbAPIKey; + } + + private static void setOmdbAPIKey(String omdbAPIKey) { + XMLController.omdbAPIKey = omdbAPIKey; + } +} diff --git a/src/main/java/kellerkinder/HomeFlix/datatypes/DatabaseDataType.java b/src/main/java/kellerkinder/HomeFlix/datatypes/DatabaseDataType.java new file mode 100644 index 0000000..cabd80e --- /dev/null +++ b/src/main/java/kellerkinder/HomeFlix/datatypes/DatabaseDataType.java @@ -0,0 +1,95 @@ +/** + * 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.datatypes; + +public class DatabaseDataType { + + private String streamUrl; + private String title; + private String season; + private String episode; + private int favorite; + private double currentTime; + + public DatabaseDataType() { + // constructor stub + } + + public DatabaseDataType(String streamUrl, String title, String season, String episode, int favorite, double currentTime) { + this.streamUrl = streamUrl; + this.title = title; + this.season = season; + this.episode = episode; + this.favorite = favorite; + this.currentTime = currentTime; + } + + public String getStreamUrl() { + return streamUrl; + } + + public void setStreamUrl(String streamUrl) { + this.streamUrl = streamUrl; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getSeason() { + return season; + } + + public void setSeason(String season) { + this.season = season; + } + + public String getEpisode() { + return episode; + } + + public void setEpisode(String episode) { + this.episode = episode; + } + + public int getFavorite() { + return favorite; + } + + public void setFavorite(int favorite) { + this.favorite = favorite; + } + + public double getCurrentTime() { + return currentTime; + } + + public void setCurrentTime(double currentTime) { + this.currentTime = currentTime; + } + +} diff --git a/src/main/java/kellerkinder/HomeFlix/datatypes/FilmTabelDataType.java b/src/main/java/kellerkinder/HomeFlix/datatypes/FilmTabelDataType.java index c2b86da..53ab588 100644 --- a/src/main/java/kellerkinder/HomeFlix/datatypes/FilmTabelDataType.java +++ b/src/main/java/kellerkinder/HomeFlix/datatypes/FilmTabelDataType.java @@ -1,7 +1,7 @@ /** * Project-HomeFlix * - * Copyright 2016-2018 <@Seil0> + * 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 @@ -20,21 +20,11 @@ */ package kellerkinder.HomeFlix.datatypes; -import javafx.beans.property.BooleanProperty; -import javafx.beans.property.SimpleBooleanProperty; -import javafx.beans.property.SimpleObjectProperty; -import javafx.beans.property.SimpleStringProperty; -import javafx.beans.property.StringProperty; -import javafx.scene.image.ImageView; - public class FilmTabelDataType { - private final StringProperty streamUrl = new SimpleStringProperty(); - private final StringProperty title = new SimpleStringProperty(); - private final StringProperty season = new SimpleStringProperty(); - private final StringProperty episode = new SimpleStringProperty(); - private final BooleanProperty favorite = new SimpleBooleanProperty(); - private final BooleanProperty cached = new SimpleBooleanProperty(); - private final SimpleObjectProperty image = new SimpleObjectProperty<>(); + private String streamUrl; + private String title; + private String season; + private String episode; /** * tableData is the data-type of tree-table-view @@ -42,106 +32,44 @@ public class FilmTabelDataType { * @param title title of the film * @param season season if it's a series * @param episode episode if it's a series - * @param favorite indicator for favorites, used for sorting the items - * @param cached indicator for caching status - * @param image favorite icon */ - public FilmTabelDataType(final String streamUrl, final String title, final String season, final String episode, - final boolean favorite, final boolean cached, final ImageView image) { - this.streamUrl.set(streamUrl); - this.title.set(title); - this.season.set(season); - this.episode.set(episode); - this.favorite.set(favorite); - this.cached.set(cached); - this.image.set(image); + public FilmTabelDataType(String streamUrl, String title, String season, String episode) { + this.streamUrl = streamUrl; + this.title = title; + this.season = season; + this.episode = episode; } - public StringProperty streamUrlProperty(){ + public String getStreamUrl() { return streamUrl; } - public StringProperty titleProperty(){ + public String getTitle() { return title; } - - public StringProperty seasonProperty(){ + + public String getSeason() { return season; } - public StringProperty episodeProperty(){ + public String getEpisode() { return episode; } - public BooleanProperty favoriteProperty(){ - return favorite; - } - - public BooleanProperty cachedProperty(){ - return cached; - } - - public SimpleObjectProperty imageProperty(){ - return image; - } - - - public final String getStreamUrl() { - return streamUrlProperty().get(); - } - - public final String getTitle() { - return titleProperty().get(); + public void setStreamUrl(String streamUrl) { + this.streamUrl = streamUrl; } - public final String getSeason() { - return seasonProperty().get(); - } - - public final String getEpisode() { - return episodeProperty().get(); - } - - public final boolean getFavorite() { - return favoriteProperty().get(); - } - - public final boolean getCached(){ - return cachedProperty().get(); - } - - public final ImageView getImage() { - return imageProperty().get(); - } - - - public final void setStreamUrl(String streamUrl) { - streamUrlProperty().set(streamUrl); + public void setTitle(String title) { + this.title = title; } - public final void setTitle(String title) { - titleProperty().set(title); - } - - public final void setSeason(String season) { - seasonProperty().set(season); + public void setSeason(String season) { + this.season = season; } - public final void setEpisode(String season) { - episodeProperty().set(season); + public void setEpisode(String episode) { + this.episode = episode; } - - public final void setFavorite(boolean favorite) { - favoriteProperty().set(favorite); - } - - public final void setCached(boolean cached){ - cachedProperty().set(cached); - } - - public final void setImage(ImageView image) { - imageProperty().set(image); - } - } diff --git a/src/main/java/kellerkinder/HomeFlix/datatypes/OMDbAPIResponseDataType.java b/src/main/java/kellerkinder/HomeFlix/datatypes/OMDbAPIResponseDataType.java new file mode 100644 index 0000000..40d78b0 --- /dev/null +++ b/src/main/java/kellerkinder/HomeFlix/datatypes/OMDbAPIResponseDataType.java @@ -0,0 +1,268 @@ +/** + * Project-HomeFlix + * + * Copyright 2018-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.datatypes; + +public class OMDbAPIResponseDataType { + + private String title; + private String year; + private String rated; + private String released; + private String season; + private String episode; + private String runtime; + private String genre; + private String director; + private String writer; + private String actors; + private String plot; + private String language; + private String country; + private String awards; + private String poster; + private String metascore; + private String imdbRating; + private String imdbVotes; + private String imdbID; + private String type; + private String dvd; + private String boxOffice; + private String production; + private String website; + private String response; + + /** + * OMDbAPIResponseDataType is the data-type for the omdbAPI response + */ + public OMDbAPIResponseDataType() { + // Auto-generated constructor stub + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getYear() { + return year; + } + + public void setYear(String year) { + this.year = year; + } + + public String getRated() { + return rated; + } + + public void setRated(String rated) { + this.rated = rated; + } + + public String getReleased() { + return released; + } + + public void setReleased(String released) { + this.released = released; + } + + public String getSeason() { + return season; + } + + public void setSeason(String season) { + this.season = season; + } + + public String getEpisode() { + return episode; + } + + public void setEpisode(String episode) { + this.episode = episode; + } + + public String getRuntime() { + return runtime; + } + + public void setRuntime(String runtime) { + this.runtime = runtime; + } + + public String getGenre() { + return genre; + } + + public void setGenre(String genre) { + this.genre = genre; + } + + public String getDirector() { + return director; + } + + public void setDirector(String director) { + this.director = director; + } + + public String getWriter() { + return writer; + } + + public void setWriter(String writer) { + this.writer = writer; + } + + public String getActors() { + return actors; + } + + public void setActors(String actors) { + this.actors = actors; + } + + public String getPlot() { + return plot; + } + + public void setPlot(String plot) { + this.plot = plot; + } + + public String getLanguage() { + return language; + } + + public void setLanguage(String language) { + this.language = language; + } + + public String getCountry() { + return country; + } + + public void setCountry(String country) { + this.country = country; + } + + public String getAwards() { + return awards; + } + + public void setAwards(String awards) { + this.awards = awards; + } + + public String getPoster() { + return poster; + } + + public void setPoster(String poster) { + this.poster = poster; + } + + public String getMetascore() { + return metascore; + } + + public void setMetascore(String metascore) { + this.metascore = metascore; + } + + public String getImdbRating() { + return imdbRating; + } + + public void setImdbRating(String imdbRating) { + this.imdbRating = imdbRating; + } + + public String getImdbVotes() { + return imdbVotes; + } + + public void setImdbVotes(String imdbVotes) { + this.imdbVotes = imdbVotes; + } + + public String getImdbID() { + return imdbID; + } + + public void setImdbID(String imdbID) { + this.imdbID = imdbID; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getDvd() { + return dvd; + } + + public void setDvd(String dvd) { + this.dvd = dvd; + } + + public String getBoxOffice() { + return boxOffice; + } + + public void setBoxOffice(String boxOffice) { + this.boxOffice = boxOffice; + } + + public String getProduction() { + return production; + } + + public void setProduction(String production) { + this.production = production; + } + + public String getWebsite() { + return website; + } + + public void setWebsite(String website) { + this.website = website; + } + + public String getResponse() { + return response; + } + + public void setResponse(String response) { + this.response = response; + } +} diff --git a/src/main/java/kellerkinder/HomeFlix/datatypes/PosterModeElement.java b/src/main/java/kellerkinder/HomeFlix/datatypes/PosterModeElement.java new file mode 100644 index 0000000..621d350 --- /dev/null +++ b/src/main/java/kellerkinder/HomeFlix/datatypes/PosterModeElement.java @@ -0,0 +1,107 @@ +/** + * 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.datatypes; + +import com.jfoenix.controls.JFXButton; + +import javafx.geometry.Insets; +import javafx.scene.control.Label; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.VBox; +import javafx.scene.text.Font; +import javafx.scene.text.FontWeight; + +public class PosterModeElement extends VBox{ + + private String streamURL; + private String title; + private Label label = new Label(); + private JFXButton button = new JFXButton(); + private ImageView imageView = new ImageView(); + + public PosterModeElement() { + super.getChildren().addAll(label, button); + + label.setMaxWidth(200); + label.setPadding(new Insets(0,0,0,8)); + label.setFont(Font.font("System", FontWeight.BOLD, 14)); + + imageView.setFitHeight(300); + imageView.setFitWidth(200); + + button.setStyle("-fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.8), 10, 0, 0, 3); "); + button.setGraphic(imageView); + } + + public PosterModeElement(String streamURL, String title, Image poster) { + this(); + + this.streamURL = streamURL; + this.title = title; + + label.setText(title); + imageView.setImage(poster); + } + + public String getStreamURL() { + return streamURL; + } + + public String getTitle() { + return title; + } + + public Label getLabel() { + return label; + } + + public JFXButton getButton() { + return button; + } + + public ImageView getImageView() { + return imageView; + } + + public void setStreamURL(String streamURL) { + this.streamURL = streamURL; + } + + public void setTitle(String title) { + this.title = title; + } + + public void setLabel(Label label) { + this.label = label; + } + + public void setButton(JFXButton button) { + this.button = button; + } + + public void setImageView(ImageView imageView) { + this.imageView = imageView; + } + +} diff --git a/src/main/java/kellerkinder/HomeFlix/datatypes/SeriresDVEpisode.java b/src/main/java/kellerkinder/HomeFlix/datatypes/SeriresDVEpisode.java new file mode 100644 index 0000000..a181884 --- /dev/null +++ b/src/main/java/kellerkinder/HomeFlix/datatypes/SeriresDVEpisode.java @@ -0,0 +1,67 @@ +package kellerkinder.HomeFlix.datatypes; + +import javafx.scene.control.Label; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.input.MouseEvent; +import javafx.scene.layout.AnchorPane; +import kellerkinder.HomeFlix.player.Player; + +public class SeriresDVEpisode extends AnchorPane { + + private String streamURL; + private Label label = new Label(); + private ImageView imageView = new ImageView(); + + public SeriresDVEpisode() { + super.getChildren().addAll(imageView, label); + super.prefWidth(200); + super.prefHeight(112); + + + imageView.setPreserveRatio(true); + imageView.setFitHeight(112); + + label.setStyle("-fx-text-fill: #ffffff; -fx-font-size: 14pt ; -fx-font-weight: bold;"); + super.setTopAnchor(label, 3.0); + super.setLeftAnchor(label, 7.0); + } + + public SeriresDVEpisode(String streamURL, String episode, Image poster) { + this(); + + this.streamURL = streamURL; + + label.setText(episode); + imageView.setImage(poster); + imageView.addEventHandler(MouseEvent.MOUSE_PRESSED, (e) -> { + // Always play with the integrated Player TODO + new Player(streamURL); + }); + } + + public String getStreamURL() { + return streamURL; + } + + public Label getLabel() { + return label; + } + + public ImageView getImageView() { + return imageView; + } + + public void setStreamURL(String streamURL) { + this.streamURL = streamURL; + } + + public void setLabel(Label label) { + this.label = label; + } + + public void setImageView(ImageView imageView) { + this.imageView = imageView; + } + +} diff --git a/src/main/java/kellerkinder/HomeFlix/datatypes/SourceDataType.java b/src/main/java/kellerkinder/HomeFlix/datatypes/SourceDataType.java index fec991a..7f16265 100644 --- a/src/main/java/kellerkinder/HomeFlix/datatypes/SourceDataType.java +++ b/src/main/java/kellerkinder/HomeFlix/datatypes/SourceDataType.java @@ -1,7 +1,7 @@ /** * Project-HomeFlix * - * Copyright 2016-2018 <@Seil0> + * 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 diff --git a/src/main/java/kellerkinder/HomeFlix/player/Player.java b/src/main/java/kellerkinder/HomeFlix/player/Player.java index 1c8d946..4d3c297 100644 --- a/src/main/java/kellerkinder/HomeFlix/player/Player.java +++ b/src/main/java/kellerkinder/HomeFlix/player/Player.java @@ -1,7 +1,7 @@ /** * Project-HomeFlix * - * Copyright 2016-2018 <@Seil0> + * 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 @@ -19,50 +19,74 @@ * MA 02110-1301, USA. * */ + package kellerkinder.HomeFlix.player; -import javafx.event.EventHandler; +import java.awt.Desktop; +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.URLConnection; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + import javafx.fxml.FXMLLoader; -import javafx.scene.Parent; import javafx.scene.Scene; import javafx.scene.image.Image; import javafx.scene.layout.AnchorPane; import javafx.stage.Stage; -import javafx.stage.WindowEvent; import kellerkinder.HomeFlix.application.Main; -import kellerkinder.HomeFlix.application.MainWindowController; - +import kellerkinder.HomeFlix.controller.DBController; public class Player { private PlayerController playerController; private Stage stage; private AnchorPane pane; private Scene scene; + private static final Logger LOGGER = LogManager.getLogger(Player.class.getName()); + + // TODO move the players choose logic to a separate static class /** * generate a new PlayerWindow - * @param mainWindowController the MainWindowController + * @param currentTableFilm the currently selected film */ - public Player(MainWindowController mainWindowController) { + public Player(String streamURL) { + + if (isSupportedFormat(streamURL)) { + hfPlayer(streamURL); + } else { + legacyPlayer(streamURL); + } + + } + + /** + * start the integrated player + * @param streamURL + */ + private void hfPlayer(String streamURL) { + playerController = new PlayerController(this, streamURL); + try { - FXMLLoader fxmlLoader = new FXMLLoader(ClassLoader.getSystemResource("fxml/PlayerWindow.fxml")); + FXMLLoader fxmlLoader = new FXMLLoader(); + fxmlLoader.setLocation(getClass().getResource("/fxml/PlayerWindow.fxml")); + fxmlLoader.setController(playerController); pane = (AnchorPane) fxmlLoader.load(); stage = new Stage(); scene = new Scene(pane); stage.setScene(scene); stage.setTitle("HomeFlix"); stage.getIcons().add(new Image(Main.class.getResourceAsStream("/icons/Homeflix_Icon_64x64.png"))); - stage.setOnCloseRequest(new EventHandler() { - public void handle(WindowEvent we) { - mainWindowController.getDbController().setCurrentTime(mainWindowController.getCurrentStreamUrl(), - playerController.getCurrentTime()); - playerController.getMediaPlayer().stop(); - stage.close(); - } + stage.setOnCloseRequest(event -> { + DBController.getInstance().setCurrentTime(streamURL, playerController.getCurrentTime()); + playerController.getMediaPlayer().stop(); + stage.close(); }); - playerController = fxmlLoader.getController(); - playerController.init(mainWindowController, this, mainWindowController.getCurrentTableFilm()); + playerController.init(); stage.setFullScreen(true); stage.show(); @@ -70,14 +94,65 @@ public class Player { e.printStackTrace(); } } + + /** + * + */ + private void legacyPlayer(String streamURL) { + LOGGER.warn("using fallback player!"); + if (System.getProperty("os.name").contains("Linux")) { + String line; + String output = ""; + Process p; + try { + p = Runtime.getRuntime().exec("which vlc"); + BufferedReader input = new BufferedReader(new InputStreamReader(p.getInputStream())); + while ((line = input.readLine()) != null) { + output = line; + } + LOGGER.info("which vlc: " + output); + input.close(); + } catch (IOException e1) { + e1.printStackTrace(); + } + if (output.contains("which: no vlc") || output == "") { +// JFXInfoAlert vlcInfoAlert = new JFXInfoAlert("Info", +// XMLController.getLocalBundle().getString("vlcNotInstalled"), btnStyle, primaryStage); +// vlcInfoAlert.showAndWait(); + } else { + try { + new ProcessBuilder("vlc", streamURL).start(); + } catch (IOException e) { + LOGGER.warn("An error has occurred while opening the file!", e); + } + } + + } else if (System.getProperty("os.name").contains("Windows") || System.getProperty("os.name").contains("Mac OS X")) { + try { + Desktop.getDesktop().open(new File(streamURL)); + } catch (IOException e) { + LOGGER.warn("An error has occurred while opening the file!", e); + } + } else { + LOGGER.error(System.getProperty("os.name") + ", OS is not supported, please contact a developer! "); + } + } + + /** + * check if a film is supported by the HomeFlixPlayer or not this is the case if + * the mime type is mp4 + * + * @param streamURL URL of the stream you want to check + * @return true if so, false if not + */ + public static boolean isSupportedFormat(String streamURL) { + String mimeType = URLConnection.guessContentTypeFromName(streamURL); + return mimeType != null && (mimeType.contains("mp4") || mimeType.contains("vp6")); + } public Stage getStage() { return stage; } - - public Parent getPane() { - return pane; - } public Scene getScene() { return scene; diff --git a/src/main/java/kellerkinder/HomeFlix/player/PlayerController.java b/src/main/java/kellerkinder/HomeFlix/player/PlayerController.java index 40fcee6..39eed19 100644 --- a/src/main/java/kellerkinder/HomeFlix/player/PlayerController.java +++ b/src/main/java/kellerkinder/HomeFlix/player/PlayerController.java @@ -1,7 +1,7 @@ /** * Project-HomeFlix * - * Copyright 2016-2018 <@Seil0> + * 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 @@ -19,6 +19,7 @@ * MA 02110-1301, USA. * */ + package kellerkinder.HomeFlix.player; import java.io.File; @@ -46,34 +47,30 @@ import javafx.scene.media.MediaPlayer; import javafx.scene.media.MediaPlayer.Status; import javafx.scene.media.MediaView; import javafx.util.Duration; -import kellerkinder.HomeFlix.application.MainWindowController; +import kellerkinder.HomeFlix.controller.DBController; +import kellerkinder.HomeFlix.controller.XMLController; import kellerkinder.HomeFlix.datatypes.FilmTabelDataType; public class PlayerController { - @FXML - private MediaView mediaView; + @FXML private MediaView mediaView; - @FXML - private VBox bottomVBox; + @FXML private VBox bottomVBox; - @FXML - private HBox controlsHBox; + @FXML private HBox controlsHBox; - @FXML - private JFXSlider timeSlider; + @FXML private JFXSlider timeSlider; - @FXML - private JFXButton stopBtn; - - @FXML - private JFXButton playBtn; - - @FXML - private JFXButton fullscreenBtn; + @FXML private JFXButton stopBtn; + @FXML private JFXButton playBtn; + @FXML private JFXButton fullscreenBtn; + @FXML private JFXButton nextEpBtn; + + @FXML private ImageView stopIcon; + @FXML private ImageView playIcon; + @FXML private ImageView fullscreenIcon; private Player player; - private MainWindowController mainWCon; private Media media; private MediaPlayer mediaPlayer; @@ -82,28 +79,33 @@ public class PlayerController { private double seekTime = 0; private double startTime = 0; private double duration = 0; + private int season = 0; + private int episode = 0; + private int countdown = 0; private boolean mousePressed = false; private boolean showControls = true; private boolean autoplay; - private ImageView stop_black = new ImageView(new Image("icons/ic_stop_black_24dp_1x.png")); - private ImageView play_arrow_black = new ImageView(new Image("icons/ic_play_arrow_black_24dp_1x.png")); - private ImageView pause_black = new ImageView(new Image("icons/ic_pause_black_24dp_1x.png")); - private ImageView fullscreen_black = new ImageView(new Image("icons/ic_fullscreen_black_24dp_1x.png")); - private ImageView fullscreen_exit_black = new ImageView(new Image("icons/ic_fullscreen_exit_black_24dp_1x.png")); - - /** FIXME double set currentTime( - * initialize the new PlayerWindow - * @param entry the film object - * @param player the player object (needed for closing action) - * @param dbController the dbController object + private Image playArrow = new Image("icons/baseline_play_arrow_black_48dp.png"); + private Image pause = new Image("icons/baseline_pause_black_48dp.png"); + private Image fullscreen = new Image("icons/baseline_fullscreen_black_48dp.png"); + private Image fullscreenExit = new Image("icons/baseline_fullscreen_exit_black_48dp.png"); + + /** + * create a new PlayerWindow object + * @param mainWCon the MainWindowController TODO do we need this? + * @param player the player object (needed for closing action) + * @param film the film object */ - public void init(MainWindowController mainWCon, Player player, FilmTabelDataType film) { - this.mainWCon = mainWCon; + public PlayerController(Player player, String streamURL) { this.player = player; - this.film = film; - startTime = mainWCon.getDbController().getCurrentTime(film.getStreamUrl()); - autoplay = mainWCon.isAutoplay(); + this.film = DBController.getInstance().getStream(streamURL); + } + + /** + * initialize the PlayerWindow + */ + public void init() { initActions(); if (film.getStreamUrl().startsWith("http")) { @@ -111,9 +113,8 @@ public class PlayerController { } else { media = new Media(new File(film.getStreamUrl()).toURI().toString()); } - startTime = mainWCon.getDbController().getCurrentTime(film.getStreamUrl()); - autoplay = mainWCon.isAutoplay(); + // create the MediaPlayer object mediaPlayer = new MediaPlayer(media); mediaView.setPreserveRatio(true); mediaView.setMediaPlayer(mediaPlayer); @@ -124,12 +125,25 @@ public class PlayerController { width.bind(Bindings.selectDouble(mediaView.sceneProperty(), "width")); height.bind(Bindings.selectDouble(mediaView.sceneProperty(), "height")); + startTime = DBController.getInstance().getCurrentTime(film.getStreamUrl()); + autoplay = XMLController.isAutoplay(); + season = !film.getSeason().isEmpty() ? Integer.parseInt(film.getSeason()) : 0; + episode = !film.getEpisode().isEmpty() ? Integer.parseInt(film.getEpisode()) : 0; + + initMediaPlayer(); + + // set the control elements to the correct value + playIcon.setImage(pause); + fullscreenIcon.setImage(fullscreenExit); + timeSlider.setValue(0); + } + + private void initMediaPlayer() { // start the media if the player is ready mediaPlayer.setOnReady(new Runnable() { @Override public void run() { duration = media.getDuration().toMillis(); - timeSlider.setMax((duration / 1000) / 60); mediaPlayer.play(); @@ -142,23 +156,32 @@ public class PlayerController { @Override public void changed(ObservableValue observable, Duration oldValue, Duration newValue) { currentTime = newValue.toMillis(); // set the current time - int episode = !film.getEpisode().isEmpty() ? Integer.parseInt(film.getEpisode()) : 0; - int season = !film.getSeason().isEmpty() ? Integer.parseInt(film.getSeason()) : 0; - - // if we are end time -10 seconds, do autoplay, if activated - if ((duration - currentTime) < 10000 && episode != 0 && autoplay) { - autoplay = false; - mainWCon.getDbController().setCurrentTime(film.getStreamUrl(), 0); // reset old video start time - FilmTabelDataType nextFilm = mainWCon.getDbController().getNextEpisode(film.getTitle(), episode, season); - if (nextFilm != null) { - mediaPlayer.stop(); - init(mainWCon, player, nextFilm); - autoplay = true; + double timeToEnd = (duration - currentTime); + + if (timeToEnd < 20000 && episode != 0 && autoplay) { + // show 20 seconds before the end a button (next episode in 10 seconds) + if (!nextEpBtn.isVisible()) + nextEpBtn.setVisible(true); + + if (countdown != (int) ((timeToEnd - 10000) / 1000)) { + countdown = (int) ((timeToEnd - 10000) / 1000); + nextEpBtn.setText("next episode in " + countdown + " seconds"); // TODO translate + bottomVBox.setVisible(true); } - } else if ((duration - currentTime) < 120) { - // if we are -20ms stop the media + + // if we are end time -10 seconds, do autoplay, if activated + if (timeToEnd < 10000) { + nextEpBtn.setVisible(false); + autoPlayNewFilm(); + } + } else if (timeToEnd < 120) { + // if we are 120ms to the end stop the media mediaPlayer.stop(); - mainWCon.getDbController().setCurrentTime(film.getStreamUrl(), 0); // reset old video start time + DBController.getInstance().setCurrentTime(film.getStreamUrl(), 0); // reset old video start time + playIcon.setImage(playArrow); + } else { + if (nextEpBtn.isVisible()) + nextEpBtn.setVisible(false); } if (!mousePressed) { @@ -167,11 +190,6 @@ public class PlayerController { } }); - // set the control elements to the correct value - stopBtn.setGraphic(stop_black); - playBtn.setGraphic(pause_black); - fullscreenBtn.setGraphic(fullscreen_exit_black); - timeSlider.setValue(0); } /** @@ -183,7 +201,7 @@ public class PlayerController { // hide controls timer initialization final Timer timer = new Timer(); TimerTask controlAnimationTask = null; // task to execute save operation - final long delayTime = 2000; // hide the controls after 2 seconds + final long delayTime = 3000; // hide the controls after 2 seconds @Override public void handle(MouseEvent mouseEvent) { @@ -237,7 +255,7 @@ public class PlayerController { @FXML void stopBtnAction(ActionEvent event) { - mainWCon.getDbController().setCurrentTime(film.getStreamUrl(), currentTime); + DBController.getInstance().setCurrentTime(film.getStreamUrl(), currentTime); mediaPlayer.stop(); player.getStage().close(); } @@ -246,22 +264,38 @@ public class PlayerController { void fullscreenBtnAction(ActionEvent event) { if (player.getStage().isFullScreen()) { player.getStage().setFullScreen(false); - fullscreenBtn.setGraphic(fullscreen_black); + fullscreenIcon.setImage(fullscreen); } else { player.getStage().setFullScreen(true); - fullscreenBtn.setGraphic(fullscreen_exit_black); + fullscreenIcon.setImage(fullscreenExit); } } @FXML void playBtnAction(ActionEvent event) { - if (mediaPlayer.getStatus().equals(Status.PLAYING)) { mediaPlayer.pause(); - playBtn.setGraphic(play_arrow_black); + playIcon.setImage(playArrow); } else { mediaPlayer.play(); - playBtn.setGraphic(pause_black); + playIcon.setImage(pause); + } + } + + @FXML + void nextEpBtnAction(ActionEvent event) { + autoPlayNewFilm(); + } + + private void autoPlayNewFilm() { + autoplay = false; + DBController.getInstance().setCurrentTime(film.getStreamUrl(), 0); // reset old video start time + FilmTabelDataType nextFilm = DBController.getInstance().getNextEpisode(film.getTitle(), episode, season); + if (nextFilm != null) { + mediaPlayer.stop(); + film = nextFilm; + init(); + autoplay = true; } } diff --git a/src/main/java/org/kellerkinder/Alerts/JFX2BtnCancelAlert.java b/src/main/java/org/kellerkinder/Alerts/JFX2BtnCancelAlert.java index 63b8ff2..cab44da 100644 --- a/src/main/java/org/kellerkinder/Alerts/JFX2BtnCancelAlert.java +++ b/src/main/java/org/kellerkinder/Alerts/JFX2BtnCancelAlert.java @@ -1,7 +1,7 @@ /** * Kellerkinder Framework Alerts * - * Copyright 2018 <@Seil0> + * Copyright 2018-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 @@ -18,6 +18,7 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ + package org.kellerkinder.Alerts; import com.jfoenix.controls.JFXAlert; @@ -88,13 +89,8 @@ public class JFX2BtnCancelAlert { JFXButton cancelBtn = new JFXButton(); cancelBtn.setText(cancelText); - cancelBtn.setOnAction(new EventHandler() { - @Override - public void handle(ActionEvent event) { - alert.close(); - System.exit(1); - } - }); + cancelBtn.addEventHandler(ActionEvent.ACTION, (e)-> alert.close()); + cancelBtn.setButtonType(com.jfoenix.controls.JFXButton.ButtonType.RAISED); cancelBtn.setPrefHeight(32); cancelBtn.setStyle(btnStyle); @@ -103,6 +99,17 @@ public class JFX2BtnCancelAlert { content.setActions(btnOne, btnTwo, cancelBtn); content.setHeading(new Text(headingText)); content.setBody(new Text(bodyText)); + + // only on first start + if (stage == null) { + cancelBtn.addEventHandler(ActionEvent.ACTION, (e)-> System.exit(0)); + Stage stage = (Stage) alert.getDialogPane().getScene().getWindow(); + stage.setMinWidth(416); + stage.setMinHeight(162); + stage.setOnCloseRequest(event -> System.exit(0)); + } + + alert.setContent(content); alert.showAndWait(); } diff --git a/src/main/java/org/kellerkinder/Alerts/JFXInfoAlert.java b/src/main/java/org/kellerkinder/Alerts/JFXInfoAlert.java index ab31c4d..79d045b 100644 --- a/src/main/java/org/kellerkinder/Alerts/JFXInfoAlert.java +++ b/src/main/java/org/kellerkinder/Alerts/JFXInfoAlert.java @@ -1,7 +1,7 @@ /** * Kellerkinder Framework Alerts * - * Copyright 2018 <@Seil0> + * Copyright 2018-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 @@ -18,6 +18,7 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ + package org.kellerkinder.Alerts; import com.jfoenix.controls.JFXAlert; @@ -25,7 +26,6 @@ import com.jfoenix.controls.JFXButton; import com.jfoenix.controls.JFXDialogLayout; import javafx.event.ActionEvent; -import javafx.event.EventHandler; import javafx.scene.text.Text; import javafx.stage.Stage; @@ -58,12 +58,7 @@ public class JFXInfoAlert { JFXAlert alert = new JFXAlert<>(stage); JFXButton button = new JFXButton("Okay"); - button.setOnAction(new EventHandler() { - @Override - public void handle(ActionEvent event) { - alert.close(); - } - }); + button.addEventHandler(ActionEvent.ACTION, (e)-> alert.close()); button.setButtonType(com.jfoenix.controls.JFXButton.ButtonType.RAISED); button.setPrefHeight(32); button.setStyle(btnStyle); diff --git a/src/main/resources/css/MainWindow.css b/src/main/resources/css/MainWindow.css index 92eccee..e17c481 100644 --- a/src/main/resources/css/MainWindow.css +++ b/src/main/resources/css/MainWindow.css @@ -1,6 +1,8 @@ -/* - * HAMBURGER CSS - */ +/******************************************************************************* + * * + * Hamburger Menu * + * * + ******************************************************************************/ .jfx-hamburgerW StackPane { -fx-background-color: white; @@ -12,9 +14,12 @@ -fx-background-radius: 5px; } -/* - * TREE TABLE CSS - */ + +/******************************************************************************* + * * + * TreeTable * + * * + ******************************************************************************/ .tree-table-view { -fx-tree-table-color: rgba(0, 168, 204, 0.2); @@ -84,9 +89,11 @@ -fx-padding: 1; /* 0.083333em; */ } -/* - * ChoiceBox - */ +/******************************************************************************* + * * + * ChoiceBox * + * * + ******************************************************************************/ .choice-box { -fx-background-color: transparent; @@ -106,4 +113,100 @@ .menu-item:focused { -fx-background-color: #EE3523; -} \ No newline at end of file +} + +/******************************************************************************* + * * + * ScrollBar * + * * + ******************************************************************************/ + + .scroll-bar:vertical, .scroll-bar:horizontal { + -fx-background-color: transparent; + } + +.scroll-bar:vertical > .track-background, .scroll-bar:horizontal > .track-background { + -fx-background-color: transparent; + -fx-background-insets: 0.0; +} + +.scroll-bar:vertical > .thumb { + -fx-background-color: #BCBCBC; + -fx-background-insets: 0.0; + -fx-background-radius: 15.0; +} + +.scroll-bar:horizontal > .thumb { + -fx-background-color: #FFFFFF; + -fx-background-insets: 0.0; + -fx-background-radius: 15.0; +} + +/* Up- and Down-Button Padding */ +.scroll-bar:vertical > .increment-button, .scroll-bar:vertical > .decrement-button { + -fx-padding: 5 2 5 2; +} + +/* Left- and Right-Button Padding */ +.scroll-bar:horizontal > .increment-button, .scroll-bar:horizontal > .decrement-button { + -fx-background-color: transparent; + -fx-padding: 5 2 5 2; +} + +.scroll-bar > .increment-button, .scroll-bar > .decrement-button, .scroll-bar:hover > .increment-button, .scroll-bar:hover > .decrement-button { + -fx-background-color: transparent; +} + +.scroll-bar > .increment-button > .increment-arrow, .scroll-bar > .decrement-button > .decrement-arrow { + -fx-background-color: rgb(150.0, 150.0, 150.0); +} + +/* Up Arrow */ +.scroll-bar:vertical > .increment-button > .increment-arrow { + -fx-shape: "M298 426h428l-214 214z"; +} + +/* Down Arrow */ +.scroll-bar:vertical > .decrement-button > .decrement-arrow { + -fx-shape: "M298 598l214-214 214 214h-428z"; +} + +/* Right Arrow */ +.scroll-bar:horizontal > .increment-button > .increment-arrow { + /* -fx-shape: "M0 428l0 -428l214 214l-214 214z"; */ + -fx-background-color:transparent; + -fx-shape: " "; + -fx-padding: 0; +} + +/* Left Arrow */ +.scroll-bar:horizontal > .decrement-button > .decrement-arrow { + /* -fx-shape: "M214 0l0 428l-214 -214l214 -214z"; */ + -fx-background-color:transparent; + -fx-shape: " "; + -fx-padding: 0; +} + +/******************************************************************************* + * * + * ScrollPane * + * * + ******************************************************************************/ + +.scroll-pane { + -fx-background-insets: 0; + -fx-padding: 0; +} + +.scroll-pane:focused { + -fx-background-insets: 0; +} + +.scroll-pane .corner { + -fx-background-insets: 0; +} + +.scroll-pane > .viewport { + -fx-background-color: transparent; +} + diff --git a/src/main/resources/fxml/FilmDetailView.fxml b/src/main/resources/fxml/FilmDetailView.fxml new file mode 100644 index 0000000..8034899 --- /dev/null +++ b/src/main/resources/fxml/FilmDetailView.fxml @@ -0,0 +1,216 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/fxml/MainWindow.fxml b/src/main/resources/fxml/MainWindow.fxml index 45b120a..a8b5461 100644 --- a/src/main/resources/fxml/MainWindow.fxml +++ b/src/main/resources/fxml/MainWindow.fxml @@ -1,174 +1,29 @@ - - - - - - - - - - - - + - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + @@ -178,7 +33,7 @@ - + diff --git a/src/main/resources/fxml/PlayerWindow.fxml b/src/main/resources/fxml/PlayerWindow.fxml index 9008334..40a58b4 100644 --- a/src/main/resources/fxml/PlayerWindow.fxml +++ b/src/main/resources/fxml/PlayerWindow.fxml @@ -3,12 +3,15 @@ + + + - + @@ -24,15 +27,49 @@ - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/fxml/SeriesDetailView.fxml b/src/main/resources/fxml/SeriesDetailView.fxml new file mode 100644 index 0000000..be1a12b --- /dev/null +++ b/src/main/resources/fxml/SeriesDetailView.fxml @@ -0,0 +1,167 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/fxml/SettingsView.fxml b/src/main/resources/fxml/SettingsView.fxml new file mode 100644 index 0000000..62f05e6 --- /dev/null +++ b/src/main/resources/fxml/SettingsView.fxml @@ -0,0 +1,154 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/icons/baseline_favorite_black_48dp.png b/src/main/resources/icons/baseline_favorite_black_48dp.png new file mode 100755 index 0000000..41caa7e Binary files /dev/null and b/src/main/resources/icons/baseline_favorite_black_48dp.png differ diff --git a/src/main/resources/icons/baseline_favorite_border_black_48dp.png b/src/main/resources/icons/baseline_favorite_border_black_48dp.png new file mode 100755 index 0000000..a696cfd Binary files /dev/null and b/src/main/resources/icons/baseline_favorite_border_black_48dp.png differ diff --git a/src/main/resources/icons/baseline_folder_black_48dp.png b/src/main/resources/icons/baseline_folder_black_48dp.png new file mode 100755 index 0000000..3cb81f8 Binary files /dev/null and b/src/main/resources/icons/baseline_folder_black_48dp.png differ diff --git a/src/main/resources/icons/baseline_fullscreen_black_48dp.png b/src/main/resources/icons/baseline_fullscreen_black_48dp.png new file mode 100644 index 0000000..917e418 Binary files /dev/null and b/src/main/resources/icons/baseline_fullscreen_black_48dp.png differ diff --git a/src/main/resources/icons/baseline_fullscreen_exit_black_48dp.png b/src/main/resources/icons/baseline_fullscreen_exit_black_48dp.png new file mode 100644 index 0000000..5fc3166 Binary files /dev/null and b/src/main/resources/icons/baseline_fullscreen_exit_black_48dp.png differ diff --git a/src/main/resources/icons/baseline_keyboard_arrow_down_black_48dp.png b/src/main/resources/icons/baseline_keyboard_arrow_down_black_48dp.png new file mode 100755 index 0000000..f984862 Binary files /dev/null and b/src/main/resources/icons/baseline_keyboard_arrow_down_black_48dp.png differ diff --git a/src/main/resources/icons/baseline_keyboard_arrow_down_white_48dp.png b/src/main/resources/icons/baseline_keyboard_arrow_down_white_48dp.png new file mode 100755 index 0000000..f31e4c8 Binary files /dev/null and b/src/main/resources/icons/baseline_keyboard_arrow_down_white_48dp.png differ diff --git a/src/main/resources/icons/baseline_keyboard_arrow_down_white_48dp_48x16.png b/src/main/resources/icons/baseline_keyboard_arrow_down_white_48dp_48x16.png new file mode 100644 index 0000000..e4c34b3 Binary files /dev/null and b/src/main/resources/icons/baseline_keyboard_arrow_down_white_48dp_48x16.png differ diff --git a/src/main/resources/icons/baseline_list_black_48dp.png b/src/main/resources/icons/baseline_list_black_48dp.png new file mode 100755 index 0000000..3351e6f Binary files /dev/null and b/src/main/resources/icons/baseline_list_black_48dp.png differ diff --git a/src/main/resources/icons/baseline_pause_black_48dp.png b/src/main/resources/icons/baseline_pause_black_48dp.png new file mode 100755 index 0000000..16d26b3 Binary files /dev/null and b/src/main/resources/icons/baseline_pause_black_48dp.png differ diff --git a/src/main/resources/icons/baseline_play_arrow_black_48dp.png b/src/main/resources/icons/baseline_play_arrow_black_48dp.png new file mode 100755 index 0000000..30967bc Binary files /dev/null and b/src/main/resources/icons/baseline_play_arrow_black_48dp.png differ diff --git a/src/main/resources/icons/baseline_stop_black_48dp.png b/src/main/resources/icons/baseline_stop_black_48dp.png new file mode 100755 index 0000000..dde8494 Binary files /dev/null and b/src/main/resources/icons/baseline_stop_black_48dp.png differ diff --git a/src/main/resources/icons/ic_favorite_black_18dp_1x.png b/src/main/resources/icons/ic_favorite_black_18dp_1x.png deleted file mode 100644 index 69be1f5..0000000 Binary files a/src/main/resources/icons/ic_favorite_black_18dp_1x.png and /dev/null differ diff --git a/src/main/resources/icons/ic_favorite_border_black_18dp_1x.png b/src/main/resources/icons/ic_favorite_border_black_18dp_1x.png deleted file mode 100644 index 7edb675..0000000 Binary files a/src/main/resources/icons/ic_favorite_border_black_18dp_1x.png and /dev/null differ diff --git a/src/main/resources/icons/ic_fullscreen_black_24dp_1x.png b/src/main/resources/icons/ic_fullscreen_black_24dp_1x.png deleted file mode 100644 index 3553d6a..0000000 Binary files a/src/main/resources/icons/ic_fullscreen_black_24dp_1x.png and /dev/null differ diff --git a/src/main/resources/icons/ic_fullscreen_exit_black_24dp_1x.png b/src/main/resources/icons/ic_fullscreen_exit_black_24dp_1x.png deleted file mode 100644 index c839448..0000000 Binary files a/src/main/resources/icons/ic_fullscreen_exit_black_24dp_1x.png and /dev/null differ diff --git a/src/main/resources/icons/ic_pause_black_24dp_1x.png b/src/main/resources/icons/ic_pause_black_24dp_1x.png deleted file mode 100644 index 6145664..0000000 Binary files a/src/main/resources/icons/ic_pause_black_24dp_1x.png and /dev/null differ diff --git a/src/main/resources/icons/ic_play_arrow_black_18dp_1x.png b/src/main/resources/icons/ic_play_arrow_black_18dp_1x.png deleted file mode 100644 index 96021dc..0000000 Binary files a/src/main/resources/icons/ic_play_arrow_black_18dp_1x.png and /dev/null differ diff --git a/src/main/resources/icons/ic_play_arrow_black_24dp_1x.png b/src/main/resources/icons/ic_play_arrow_black_24dp_1x.png deleted file mode 100644 index d78c57b..0000000 Binary files a/src/main/resources/icons/ic_play_arrow_black_24dp_1x.png and /dev/null differ diff --git a/src/main/resources/icons/ic_play_arrow_white_18dp_1x.png b/src/main/resources/icons/ic_play_arrow_white_18dp_1x.png deleted file mode 100644 index cae85ab..0000000 Binary files a/src/main/resources/icons/ic_play_arrow_white_18dp_1x.png and /dev/null differ diff --git a/src/main/resources/icons/ic_search_black_18dp_1x.png b/src/main/resources/icons/ic_search_black_18dp_1x.png deleted file mode 100644 index f0d4e97..0000000 Binary files a/src/main/resources/icons/ic_search_black_18dp_1x.png and /dev/null differ diff --git a/src/main/resources/icons/ic_skip_next_black_18dp_1x.png b/src/main/resources/icons/ic_skip_next_black_18dp_1x.png deleted file mode 100644 index 327fd8d..0000000 Binary files a/src/main/resources/icons/ic_skip_next_black_18dp_1x.png and /dev/null differ diff --git a/src/main/resources/icons/ic_skip_next_white_18dp_1x.png b/src/main/resources/icons/ic_skip_next_white_18dp_1x.png deleted file mode 100644 index 26434a0..0000000 Binary files a/src/main/resources/icons/ic_skip_next_white_18dp_1x.png and /dev/null differ diff --git a/src/main/resources/icons/ic_skip_previous_black_18dp_1x.png b/src/main/resources/icons/ic_skip_previous_black_18dp_1x.png deleted file mode 100644 index 34c528d..0000000 Binary files a/src/main/resources/icons/ic_skip_previous_black_18dp_1x.png and /dev/null differ diff --git a/src/main/resources/icons/ic_skip_previous_white_18dp_1x.png b/src/main/resources/icons/ic_skip_previous_white_18dp_1x.png deleted file mode 100644 index e7d7643..0000000 Binary files a/src/main/resources/icons/ic_skip_previous_white_18dp_1x.png and /dev/null differ diff --git a/src/main/resources/icons/ic_stop_black_24dp_1x.png b/src/main/resources/icons/ic_stop_black_24dp_1x.png deleted file mode 100644 index 0588f0b..0000000 Binary files a/src/main/resources/icons/ic_stop_black_24dp_1x.png and /dev/null differ diff --git a/src/main/resources/locals/HomeFlix-Local_de_DE.properties b/src/main/resources/locals/HomeFlix-Local_de_DE.properties index 166e686..1b7279d 100644 --- a/src/main/resources/locals/HomeFlix-Local_de_DE.properties +++ b/src/main/resources/locals/HomeFlix-Local_de_DE.properties @@ -22,6 +22,7 @@ updateBtnNoUpdateAvailable = Kein Update verf\u00FCgbar autoUpdate = beim Start nach Updates suchen: autoplay = autoplay branchLbl = Updatezweig +sourcesLbl = Quellen #column translations columnStreamUrl = Datei Name @@ -31,25 +32,20 @@ columnEpisode = Episode columnFavorite = Favorit #error translations -errorUpdateV = Beim ausf\u00FChren des Updates ist ein Fehler aufgetreten! \nError: could not check update version (nvc)\nWeitere Hilfe erhalten sie unter www.kellerkinder.xyz \noder wenden sie sich an support@kellerkinder.xyz -errorUpdateD = Beim ausf\u00FChren des Updates ist ein Fehler aufgetreten! \nError: could not download update files (ndf)\nWeitere Hilfe erhalten sie unter www.kellerkinder.xyz \noder wenden sie sich an support@kellerkinder.xyz -errorMode = Oh, da lief etwas falsch! Da hat jemand einen falschen Modus verwendet. \nError: mode unknow (muk)\nWeitere Hilfe erhalten sie unter www.kellerkinder.xyz \noder wenden sie sich an support@kellerkinder.xyz -errorOpenStream = Beim \u00F6ffnen des Streams ist ein Fehler aufgetreten! -errorLoad = Beim laden der Einstellungen ist ein Fehler aufgetreten! -errorSave = Beim speichern der Einstellungen ist ein Fehler aufgetreten! -noFilmFound = Kein Film mit diesem Titel gefunden! vlcNotInstalled = Um einen Film abspielen wird der VLC Media Player ben\u00F6tigt! -infoText = \nAutoren: \n \u2022 seil0@kellerkinder.xyz \n \u2022 hendrik.schutter@coptersicht.de \n(c) 2016-2018 Kellerkinder www.kellerkinder.xyz +infoText = \nAutoren: \n \u2022 seil0@mosad.xyz \n \u2022 localhorst@mosad.xyz \n(c) 2016-2019 mosad www.mosad.xyz #textFlow translations title = Titel year = Jahr -rating = Einstufung -publishedOn = Ver\u00F6ffentlicht am -duration = Laufzeit +rated = Einstufung +released = Ver\u00F6ffentlicht am +season = Staffel +episode = Episode +runtime = Laufzeit genre = Gener -director = Regisseur -writer = Autor +directors = Regisseur +writers = Autoren actors = Schauspieler plot = Beschreibung language = Original Sprache @@ -58,8 +54,14 @@ awards = Auszeichnungen metascore = Metascore imdbRating = IMDB-Bewertung type = Type +boxOffice = BoxOffice +website = Webseite #first start addSourceHeader = Neue Quelle hinzuf\u00FCgen addSourceBody = HomeFlix konnte keine Quelle finden. \nFüge eine loakels Verzeichniss oder eine Sreaming Datei als neue Quelle hinzu. cancelBtnText = Abbrechen + +#DetailView +crew = Haupt-Crew +score = Benutzerbewertung diff --git a/src/main/resources/locals/HomeFlix-Local_en_US.properties b/src/main/resources/locals/HomeFlix-Local_en_US.properties index 8b16294..a2166c7 100644 --- a/src/main/resources/locals/HomeFlix-Local_en_US.properties +++ b/src/main/resources/locals/HomeFlix-Local_en_US.properties @@ -22,6 +22,7 @@ updateBtnNoUpdateAvailable = no update available autoUpdate = check at startup for updates: autoplay = autoplay branchLbl = Branch +sourcesLbl = Quellen #column translations columnStreamUrl = File Name @@ -31,25 +32,20 @@ columnEpisode = Episode columnFavorite = Favorite #error translations -errorUpdateV = An error has occurred during update! \nError: could not check update version (nvc) \nTo get help, visit www.kellerkinder.xyz \nor contcat support@kellerkinder.xyz -errorUpdateD = An error has occurred during update! \nError: could not download update files (ndf) \nTo get help, visit www.kellerkinder.xyz \nor contcat support@kellerkinder.xyz -errorMode = Oh, something went wrong! It seems someone has used a wrong mode. \nError: mode unknow (muk) \nTo get help, visit www.kellerkinder.xyz \nor contcat support@kellerkinder.xyz -errorOpenStream = An error has occurred during opening the stream! -errorLoad = An error occurred while loading the settings! -errorSave = An error occurred while saving the settings! -noFilmFound = No film with this title found! vlcNotInstalled = VLC Media Player is required to play a movie! -infoText = \nMaintainers: \n \u2022 seil0@kellerkinder.xyz \n \u2022 hendrik.schutter@coptersicht.de \n(c) 2016-2018 Kellerkinder www.kellerkinder.xyz +infoText = \nMaintainers: \n \u2022 seil0@mosad.xyz \n \u2022 localhorst@mosad.xyz \n(c) 2016-2019 mosad www.mosad.xyz #textFlow translations title = Title year = Year -rating = Rating -publishedOn = published on -duration = Duration +rated = Rating +released = published on +season = Season +episode = Episode +runtime = Runtime genre = Gener -director = Director -writer = Writer +directors = Directors +writers = Writers actors = Actors plot = Plot language = Language @@ -58,8 +54,14 @@ awards = Awards metascore = Metascore imdbRating = IMDB-Rating type = Type +boxOffice = BoxOffice +website = Website #first start addSourceHeader = add a new source addSourceBody = HomeFlix was not able to load a source. \nAdd a new local directory oa a streaming file as new source. cancelBtnText = cancel + +#DetailView +crew = Featured Crew +score = User Score