/** * 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.FileNotFoundException; import java.io.IOException; import java.net.URLConnection; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; import java.util.List; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import javafx.collections.ObservableList; import javafx.scene.Node; 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.MainWindowController; import kellerkinder.HomeFlix.datatypes.DatabaseDataType; import kellerkinder.HomeFlix.datatypes.FilmTabelDataType; import kellerkinder.HomeFlix.datatypes.OMDbAPIResponseDataType; public class DBController { private MainWindowController mainWindowController; private String DB_PATH; 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 databaseStream = 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(MainWindowController mainWindowController) { this.mainWindowController = mainWindowController; } /** * initialize the {@link DBController} * initialize 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 ==========>"); } /** * create a new connection to the HomeFlix.db database * AutoCommit is set to false to prevent some issues, so manual commit is active! */ private void initDatabaseConnection() { 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.info("ROM database loaded successfull"); } /** * if tables don't exist create them * films table: streamUrl is primary key * cache table: streamUrl is primary key */ 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, Season, Episode ,Runtime, Genre, Director, Writer," + " Actors, Plot, Language, Country, Awards, Poster, Metascore, imdbRating, imdbVotes," + " imdbID, Type, dvd, BoxOffice, Website, Response)"); stmt.close(); } catch (SQLException e) { LOGGER.error(e); } } /** * get all database entries */ private void loadDatabase() { // get all entries from the table try { Statement stmt = connection.createStatement(); ResultSet rs = stmt.executeQuery("SELECT * FROM films"); while (rs.next()) { databaseStream.add(new DatabaseDataType(rs.getString("streamUrl"), rs.getString("title"), rs.getString("season"), rs.getString("episode"), rs.getInt("favorite"), rs.getBoolean("cached"), rs.getDouble("currentTime"))); } stmt.close(); rs.close(); } catch (SQLException e) { LOGGER.error("Ups! an error occured!", e); } } /** * load all sources */ private void loadSources() { SourcesController sourcesController = new SourcesController(mainWindowController); sourceStreams = sourcesController.loadSources(); } /** * load the data to the mainWindowController * order entries by title */ private void loadDataToFilmsList() { ImageView imageView; LOGGER.info("loading data to mwc ..."); try { //load local Data Statement stmt = connection.createStatement(); ResultSet rs = stmt.executeQuery("SELECT * FROM films ORDER BY title"); while (rs.next()) { imageView = rs.getBoolean("favorite") ? new ImageView(favorite_black) : new ImageView(favorite_border_black); mainWindowController.getFilmsList().add(new FilmTabelDataType(rs.getString("streamUrl"), rs.getString("title"), rs.getString("season"), rs.getString("episode") ,rs.getBoolean("favorite"), rs.getBoolean("cached"), imageView)); } stmt.close(); rs.close(); } catch (SQLException e) { LOGGER.error("Ups! an error occured!", e); } LOGGER.info("loading data to the GUI ..."); mainWindowController.addFilmsToTable(mainWindowController.getFilmsList()); } /** * refresh data in mainWindowController for one element * @param streamUrl of the film * @param index of the film in LocalFilms list */ public void refresh(String streamUrl, int indexList) { LOGGER.info("refresh data for " + streamUrl); try { PreparedStatement ps = connection.prepareStatement("SELECT * FROM films WHERE streamUrl = ?"); ps.setString(1, streamUrl); ResultSet rs = ps.executeQuery(); while (rs.next()) { ImageView imageView = rs.getBoolean("favorite") ? new ImageView(favorite_black) : new ImageView(favorite_border_black); mainWindowController.getFilmsList().set(indexList, new FilmTabelDataType(rs.getString("streamUrl"), rs.getString("title"), rs.getString("season"), rs.getString("episode"), rs.getBoolean("favorite"), rs.getBoolean("cached"), imageView)); } rs.close(); ps.close(); } catch (Exception e) { LOGGER.error("Ups! error while refreshing mwc!", e); } } /** * 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 ..."); // clean all ArraLists databaseStream.clear(); sourceStreams.clear(); loadSources(); // reload all sources loadDatabase(); // reload all films saved in the DB LOGGER.info("filme in db: " + databaseStream.size()); try { checkAddEntry(); checkRemoveEntry(); } catch (Exception e) { LOGGER.error("Error while refreshing the database", e); } // clear the FilmsList and FilmRoot children mainWindowController.getFilmsList().clear(); mainWindowController.getFilmRoot().getChildren().clear(); loadDataToFilmsList(); // load the new data to the FilmsList } /** * check if there are any entries that have been removed from the film-directory * @throws SQLException */ private void checkRemoveEntry() throws SQLException { PreparedStatement ps = connection.prepareStatement("DELETE FROM films WHERE streamUrl = ?"); LOGGER.info("checking for entrys to remove to DB ..."); for (DatabaseDataType dbStreamEntry : databaseStream) { // if the directory doen't contain the entry form the database, remove it // 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"); } } ps.executeBatch(); connection.commit(); ps.close(); } /** * check if there are new films in the film-directory * @throws SQLException * @throws FileNotFoundException * @throws IOException */ private void checkAddEntry() throws SQLException, FileNotFoundException, IOException { PreparedStatement ps = connection.prepareStatement("insert into films values (?, ?, ?, ?, ?, ?, ?)"); LOGGER.info("checking for entrys to add to DB ..."); // new for (DatabaseDataType sourceStreamEntry : sourceStreams) { // if databaseStream has a item where StreamUrl equals sourceStreamEntry.getStreamUrl() return it, else null DatabaseDataType result = databaseStream.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.setBoolean(6, sourceStreamEntry.getCached()); ps.setDouble(7, sourceStreamEntry.getCurrentTime()); ps.addBatch(); // adds the entry LOGGER.info("Added \"" + sourceStreamEntry.getTitle() + "\" to database"); databaseStream.add(sourceStreamEntry); } } ps.executeBatch(); connection.commit(); ps.close(); } /** * DEBUG * prints all entries from the database to the console */ public void printAllDBEntriesDEBUG() { System.out.println("Outputting all entries ... \n"); try { Statement stmt = connection.createStatement(); ResultSet rs = stmt.executeQuery("SELECT * FROM films"); while (rs.next()) { System.out.println(rs.getString("streamUrl")); System.out.println(rs.getString("title")); System.out.println(rs.getString("season")); System.out.println(rs.getString("episode")); System.out.println(rs.getString("rating")); System.out.println(rs.getString("cached")); System.out.println(rs.getString("currentTime") + "\n"); } rs.close(); stmt.close(); } catch (SQLException e) { LOGGER.error("An error occured, while printing all entries", e); } } /** * update the database entry for the given film, favorite = 0 * @param streamUrl URL of the film */ public void dislike(String streamUrl) { LOGGER.info("dislike " + streamUrl); try { PreparedStatement 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 = 1 * @param streamUrl URL of the film */ public void like(String streamUrl) { LOGGER.info("like " + streamUrl); try { PreparedStatement ps = connection.prepareStatement("UPDATE films SET favorite = 1 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, cached = 1 * @param streamUrl URL of the film */ public void setCached(String streamUrl) { try { PreparedStatement ps = connection.prepareStatement("UPDATE films SET cached = 1 WHERE streamUrl = ?"); ps.setString(1, streamUrl); ps.executeUpdate(); connection.commit(); ps.close(); } catch (SQLException e) { LOGGER.error("Ups! an error occured!", e); } refresh(streamUrl, mainWindowController.getIndexList()); } /** * add the received data to the cache table * @param streamUrl URL of the film * @param omdbResponse the response data from omdbAPI */ public void addCache(String streamUrl, OMDbAPIResponseDataType omdbResponse) { try { PreparedStatement ps = connection.prepareStatement("insert into cache values (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"); LOGGER.info("adding cache for: " + streamUrl); ps.setString(1,streamUrl); 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(); } catch (Exception e) { LOGGER.error(e); } } /** * checks if there is already a entry with the given streamUrl in the cache * @param streamUrl URL of the element * @return true if the element is already cached, else false */ public boolean searchCacheByURL(String streamUrl) { boolean retValue = false; try { PreparedStatement ps = connection.prepareStatement("SELECT * FROM cache WHERE streamUrl = ?"); ps.setString(1, streamUrl); ResultSet rs = ps.executeQuery(); retValue = rs.next(); rs.close(); ps.close(); } catch (Exception e) { LOGGER.error("Ups! error while getting the current time!", e); } return retValue; } /** * sets the cached data to mwc's TextFlow * @param streamUrl URL of the film */ public void readCache(String streamUrl) { try { PreparedStatement ps = connection.prepareStatement("SELECT * FROM cache WHERE streamUrl = ?"); ps.setString(1, streamUrl); ResultSet rs = ps.executeQuery(); Font font = Font.font("System", FontWeight.BOLD, (int) Math.round(XMLController.getFontSize())); ObservableList textFlow = mainWindowController.getTextFlow().getChildren(); ArrayList nameText = new ArrayList(); nameText.add(new Text(mainWindowController.getBundle().getString("title") + ": ")); nameText.add(new Text(mainWindowController.getBundle().getString("year") + ": ")); nameText.add(new Text(mainWindowController.getBundle().getString("rated") + ": ")); nameText.add(new Text(mainWindowController.getBundle().getString("released") + ": ")); nameText.add(new Text(mainWindowController.getBundle().getString("season") + ": ")); nameText.add(new Text(mainWindowController.getBundle().getString("episode") + ": ")); nameText.add(new Text(mainWindowController.getBundle().getString("runtime") + ": ")); nameText.add(new Text(mainWindowController.getBundle().getString("genre") + ": ")); nameText.add(new Text(mainWindowController.getBundle().getString("director") + ": ")); nameText.add(new Text(mainWindowController.getBundle().getString("writer") + ": ")); nameText.add(new Text(mainWindowController.getBundle().getString("actors") + ": ")); nameText.add(new Text(mainWindowController.getBundle().getString("plot") + ": ")); nameText.add(new Text(mainWindowController.getBundle().getString("language") + ": ")); nameText.add(new Text(mainWindowController.getBundle().getString("country") + ": ")); nameText.add(new Text(mainWindowController.getBundle().getString("awards") + ": ")); nameText.add(new Text(mainWindowController.getBundle().getString("metascore") + ": ")); nameText.add(new Text(mainWindowController.getBundle().getString("imdbRating") + ": ")); nameText.add(new Text(mainWindowController.getBundle().getString("type") + ": ")); nameText.add(new Text(mainWindowController.getBundle().getString("boxOffice") + ": ")); nameText.add(new Text(mainWindowController.getBundle().getString("website") + ": ")); // set the correct font for the nameText for (Text text : nameText) { text.setFont(font); } // clear the textFlow and all the new text textFlow.clear(); textFlow.addAll(nameText.get(0), new Text(rs.getString("Title") + "\n")); textFlow.addAll(nameText.get(1), new Text(rs.getString("Year") + "\n")); textFlow.addAll(nameText.get(2), new Text(rs.getString("Rated") + "\n")); textFlow.addAll(nameText.get(3), new Text(rs.getString("Released") + "\n")); if (rs.getString("Episode").length() > 0) { textFlow.addAll(nameText.get(4), new Text(rs.getString("Season") + "\n")); textFlow.addAll(nameText.get(5), new Text(rs.getString("Episode") + "\n")); } textFlow.addAll(nameText.get(6), new Text(rs.getString("Runtime") + "\n")); textFlow.addAll(nameText.get(7), new Text(rs.getString("Genre") + "\n")); textFlow.addAll(nameText.get(8), new Text(rs.getString("Director") + "\n")); textFlow.addAll(nameText.get(9), new Text(rs.getString("Writer") + "\n")); textFlow.addAll(nameText.get(10), new Text(rs.getString("Actors") + "\n")); textFlow.addAll(nameText.get(11), new Text(rs.getString("Plot") + "\n")); textFlow.addAll(nameText.get(12), new Text(rs.getString("Language") + "\n")); textFlow.addAll(nameText.get(13), new Text(rs.getString("Country") + "\n")); textFlow.addAll(nameText.get(14), new Text(rs.getString("Awards") + "\n")); textFlow.addAll(nameText.get(15), new Text(rs.getString("metascore") + "\n")); textFlow.addAll(nameText.get(16), new Text(rs.getString("imdbRating") + "\n")); textFlow.addAll(nameText.get(17), new Text(rs.getString("Type") + "\n")); textFlow.addAll(nameText.get(18), new Text(rs.getString("BoxOffice") + "\n")); textFlow.addAll(nameText.get(19), new Text(rs.getString("Website") + "\n")); mainWindowController.getTextFlow().setStyle("-fx-font-size : " + ((int) Math.round(XMLController.getFontSize()) + 1) + "px;"); // add the image try { mainWindowController.getPosterImageView().setImage(new Image(new File(rs.getString("Poster")).toURI().toString())); } catch (Exception e) { mainWindowController.getPosterImageView().setImage(new Image("icons/close_black_2048x2048.png")); LOGGER.error("No Poster found, useing default."); } rs.close(); ps.close(); } catch (SQLException e) { LOGGER.error("Ups! an error occured!", e); } } /** * 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"), rs.getBoolean("favorite"), rs.getBoolean("cached"), new ImageView(favorite_border_black))); } 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; } /** * return the currentTime in ms saved in the database * @param streamUrl URL of the film * @return {@link Double} currentTime in ms */ public double getCurrentTime(String streamUrl) { LOGGER.info("get currentTime: " + streamUrl); double currentTime = 0; try { PreparedStatement ps = connection.prepareStatement("SELECT * FROM films WHERE streamUrl = ?"); ps.setString(1, streamUrl); ResultSet rs = ps.executeQuery(); currentTime = rs.getDouble("currentTime"); rs.close(); ps.close(); } catch (Exception e) { LOGGER.error("Ups! error while getting the current time!", e); } return currentTime; } /** * save the currentTime to the database * @param streamUrl URL of the film * @param currentTime currentTime in ms of the film */ public void setCurrentTime(String streamUrl, double currentTime) { LOGGER.info("set currentTime: " + streamUrl); try { PreparedStatement ps = connection.prepareStatement("UPDATE films SET currentTime = ? WHERE streamUrl = ?"); ps.setDouble(1, currentTime); ps.setString(2, streamUrl); ps.executeUpdate(); connection.commit(); ps.close(); } catch (SQLException e) { LOGGER.error("Ups! error while updateing the current time!", e); } } /** * get the next episode of a series * @param title title of the film * @param episode episode currently played * @return {@link FilmTabelDataType} the next episode as object */ public FilmTabelDataType getNextEpisode(String title, int episode, int season) { FilmTabelDataType nextFilm = null; try { // 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(); /* 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 } // 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.getBoolean("favorite"), rs.getBoolean("cached"), new ImageView()); rs.close(); ps.close(); } catch (Exception e) { LOGGER.error("Ups! error while getting next episode!", e); } return nextFilm; } /** * get the last watched episode * @param title the title of the series * @return the last watched episode as {@link FilmTabelDataType} object */ public FilmTabelDataType getLastWatchedEpisode(String title) { LOGGER.info("last watched episode of: " + title); FilmTabelDataType nextFilm = null; double lastCurrentTime = 0; try { Statement stmt = connection.createStatement(); ResultSet rs = stmt.executeQuery("SELECT * FROM films WHERE title = \"" + title + "\";"); while (rs.next()) { nextFilm = new FilmTabelDataType(rs.getString("streamUrl"), rs.getString("title"), rs.getString("season"), rs.getString("episode"), rs.getBoolean("favorite"), rs.getBoolean("cached"), new ImageView()); if (rs.getDouble("currentTime") > lastCurrentTime) { lastCurrentTime = rs.getDouble("currentTime"); nextFilm = new FilmTabelDataType(rs.getString("streamUrl"), rs.getString("title"), rs.getString("season"), rs.getString("episode"), rs.getBoolean("favorite"), rs.getBoolean("cached"), new ImageView()); break; } } rs.close(); stmt.close(); } catch (Exception e) { LOGGER.error("Ups! error while getting the last watched episode!", e); } return nextFilm; } /** * check if a file is a video * @param path the path to the file * @return true if the file is a video, else false */ public static boolean isVideoFile(String path) { String mimeType = URLConnection.guessContentTypeFromName(path); return mimeType != null && mimeType.startsWith("video"); } }