/** * 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.Date; 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.FXCollections; import javafx.collections.ObservableList; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import kellerkinder.HomeFlix.datatypes.DatabaseDataType; import kellerkinder.HomeFlix.datatypes.FilmTabelDataType; import kellerkinder.HomeFlix.datatypes.OMDbAPIResponseDataType; import kellerkinder.HomeFlix.datatypes.PosterModeElement; public class DBController { private static DBController instance = null; 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 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() { // 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 */ public void init() { initDatabaseConnection(); createDatabase(); } /** * 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 HomeFlix database", e); } LOGGER.info("HomeFlix 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()) { databaseStreams.add(new DatabaseDataType(rs.getString("streamUrl"), rs.getString("title"), rs.getString("season"), rs.getString("episode"), rs.getInt("favorite"), rs.getDate("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(); sourceStreams = sourcesController.loadSources(); } /** * load all streams from the database to a ObservableList, * order entries by title * @return a ObservableList that contains all streams from the database */ 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()) { ImageView imageView = rs.getBoolean("favorite") ? new ImageView(favorite_black) : new ImageView(favorite_border_black); filmsList.add(new FilmTabelDataType(rs.getString("streamUrl"), rs.getString("title"), rs.getString("season"), rs.getString("episode") ,rs.getBoolean("favorite"), rs.getDate("cached").toLocalDate(), imageView)); } stmt.close(); rs.close(); } catch (SQLException e) { LOGGER.error("Ups! an error occured!", e); } return filmsList; } /** * 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 FilmTabelDataType getStream(String streamUrl) { FilmTabelDataType film = null; 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); film = new FilmTabelDataType(rs.getString("streamUrl"), rs.getString("title"), rs.getString("season"), rs.getString("episode"), rs.getBoolean("favorite"), rs.getDate("cached").toLocalDate(), imageView); } rs.close(); ps.close(); } catch (Exception e) { LOGGER.error("Ups! error while refreshing mwc!", e); } return film; } /** * get all entries 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 ORDER BY title"); while (rs.next()) { // create the stream object PosterModeElement posterElement = new PosterModeElement(); posterElement.setStreamURL(rs.getString("streamUrl")); // get from the cache table String[] cacheData = readCache(rs.getString("streamUrl")); posterElement.getLabel().setText(cacheData[0]); if(cacheData[20] != null) { posterElement.getImageView().setImage(new Image(new File(cacheData[20]).toURI().toString())); } else { posterElement.getImageView().setImage(new Image("/icons/Homeflix_Poster.png")); } posterElementsList.add(posterElement); } 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 */ public void refreshDataBase() { LOGGER.info("<========== starting refreshing database ==========>"); // clean all ArraLists 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(); } catch (Exception e) { LOGGER.error("Error while refreshing the database", e); } LOGGER.info("<========== finished refreshing database ==========>"); } /** * 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 : databaseStreams) { // 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 = 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, sourceStreamEntry.getCached()); 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(); } /** * 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 = ? WHERE streamUrl = ?"); ps.setDate(1, new Date(System.currentTimeMillis())); ps.setString(2, streamUrl); ps.executeUpdate(); connection.commit(); ps.close(); } catch (SQLException e) { LOGGER.error("Ups! an error occured!", e); } } /** * 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; } /** * read the cached data from the Database * @param streamUrl URL of the film * @return a String array (length = 21) with all cached data */ 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(); 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"); } rs.close(); 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"), rs.getBoolean("favorite"), rs.getDate("cached").toLocalDate(), 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 for " + 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 = " + currentTime + " for " + 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.getDate("cached").toLocalDate(), new ImageView()); rs.close(); ps.close(); } catch (Exception e) { LOGGER.error("Ups! error while getting next episode!", e); } return nextFilm; } /** 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 */ 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.getDate("cached").toLocalDate(), new ImageView()); // get the first episode where currentTime != 0 if (rs.getDouble("currentTime") > lastCurrentTime) { 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"); } }