Java tutorial
/* * Copyright (c) 2004-2016 YAMJ Members * https://github.com/orgs/YAMJ/people * * This file is part of the Yet Another Movie Jukebox (YAMJ) project. * * YAMJ 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 * any later version. * * YAMJ 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 YAMJ. If not, see <http://www.gnu.org/licenses/>. * * Web: https://github.com/YAMJ/yamj-v2 * */ package com.moviejukebox.model; import com.moviejukebox.model.comparator.MovieRatingComparator; import com.moviejukebox.model.comparator.MovieTitleComparator; import com.moviejukebox.model.comparator.MovieSetComparator; import com.moviejukebox.model.comparator.MovieReleaseComparator; import com.moviejukebox.model.comparator.LastModifiedComparator; import com.moviejukebox.model.comparator.MovieTop250Comparator; import com.moviejukebox.model.comparator.CertificationComparator; import static com.moviejukebox.tools.FileTools.createCategoryKey; import static com.moviejukebox.tools.FileTools.createPrefix; import static com.moviejukebox.tools.FileTools.makeSafeFilename; import static com.moviejukebox.tools.PropertiesUtil.FALSE; import static com.moviejukebox.tools.PropertiesUtil.TRUE; import com.moviejukebox.model.enumerations.DirtyFlag; import com.moviejukebox.model.enumerations.OverrideFlag; import com.moviejukebox.tools.*; import java.io.File; import java.text.DecimalFormat; import java.util.*; import java.util.concurrent.Callable; import java.util.concurrent.TimeUnit; import org.apache.commons.configuration.ConfigurationException; import org.apache.commons.configuration.HierarchicalConfiguration; import org.apache.commons.configuration.XMLConfiguration; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.math.NumberUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class Library implements Map<String, Movie> { private static final Logger LOG = LoggerFactory.getLogger(Library.class); // Constants public static final String TV_SERIES = "TVSeries"; public static final String SET = "Set"; // Library values private final Collection<IndexInfo> generatedIndexes = Collections .synchronizedCollection(new ArrayList<IndexInfo>()); private static boolean filterGenres; private static final boolean FILTER_CERTIFICAION; private static boolean singleSeriesPage; private static final List<String> CERTIFICATION_ORDERING = new ArrayList<>(); private static final List<String> LIBRARY_ORDERING = new ArrayList<>(); private static final Map<String, String> GENRES_MAP = new HashMap<>(); private static final Map<String, String> CERTIFICATIONS_MAP = new HashMap<>(); private static String defaultCertification = null; private static final Map<String, String> CATEGORIES_MAP = new LinkedHashMap<>(); // This is a LinkedHashMap to ensure that the order that the items are inserted into the Map is retained private static boolean charGroupEnglish = false; private final Map<Movie, String> keys = new HashMap<>(); private final Map<String, Movie> library = new TreeMap<>(); private final Map<String, Movie> extras = new TreeMap<>(); private List<Movie> moviesList = new ArrayList<>(); private final Map<String, Index> indexes; private Map<String, Index> unCompressedIndexes = new LinkedHashMap<>(); private static final DecimalFormat PADDED_FORMAT = new DecimalFormat("000"); // Issue 190 private static int categoryMinCountMaster = 3; private static int categoryMaxCountMaster = 0; private static int movieMaxCountMaster = 0; private static int maxGenresPerMovie = 3; private static int newMovieCount; private static long newMovieDays; private static int newTvCount; private static long newTvDays; private static int minSetCount = 2; private static boolean setsRequireAll = false; private static final List<String> CATEGORIES_EXPLODE_SET; private static boolean removeExplodeSet = false; private static boolean keepTVExplodeSet = true; private static boolean beforeSortExplodeSet = false; private static boolean removeTitleExplodeSet = false; private static String setsRating = "first"; private static final String INDEX_LIST; private static final List<String> AWARD_EVENT_LIST; private static final List<String> AWARD_NAME_LIST; private static final List<String> AWARD_NOMINATED; private static final List<String> AWARD_WON; private static boolean scrapeWonAwards = false; private static boolean splitHD = false; private static boolean processExtras = true; private static boolean hideWatched = true; private static final boolean ENABLE_WATCH_SCANNER; private static final List<String> DIRTY_LIBRARIES = new ArrayList<>(); // Issue 1897: Cast enhancement private final Map<String, Person> people; private boolean isDirty = false; private static boolean peopleScan = false; private static boolean peopleExclusive = false; private static boolean completePerson = true; // Static values for the year indexes private static final int CURRENT_YEAR = Calendar.getInstance().get(Calendar.YEAR); private static final int FINAL_YEAR = CURRENT_YEAR - 2; private static final int CURRENT_DECADE = (FINAL_YEAR / 10) * 10; // Sorting params private static final Map<String, String> SORT_KEYS = new HashMap<>(); private static final Map<String, Boolean> SORT_ASC = new HashMap<>(); private static final List<String> SORT_COMP = new ArrayList<>(); // Index Names public static final String INDEX_3D = "3D"; public static final String INDEX_ACTOR = "Actor"; public static final String INDEX_ALL = "All"; public static final String INDEX_AWARD = "Award"; public static final String INDEX_CAST = "Cast"; public static final String INDEX_CATEGORIES = "Categories"; public static final String INDEX_CERTIFICATION = "Certification"; public static final String INDEX_COUNTRY = "Country"; public static final String INDEX_DIRECTOR = "Director"; public static final String INDEX_EXTRAS = "Extras"; public static final String INDEX_GENRES = "Genres"; public static final String INDEX_HD = "HD"; public static final String INDEX_HD1080 = "HD-1080"; public static final String INDEX_HD720 = "HD-720"; public static final String INDEX_LIBRARY = "Library"; public static final String INDEX_MOVIES = "Movies"; public static final String INDEX_NEW = "New"; public static final String INDEX_NEW_MOVIE = "New-Movie"; public static final String INDEX_NEW_TV = "New-TV"; public static final String INDEX_OTHER = "Other"; public static final String INDEX_PERSON = "Person"; public static final String INDEX_RATING = "Rating"; public static final String INDEX_RATINGS = "Ratings"; public static final String INDEX_SET = "Set"; public static final String INDEX_SETS = "Sets"; public static final String INDEX_TITLE = "Title"; public static final String INDEX_TOP250 = "Top250"; public static final String INDEX_TVSHOWS = "TV Shows"; public static final String INDEX_UNWATCHED = "Unwatched"; public static final String INDEX_WATCHED = "Watched"; public static final String INDEX_WRITER = "Writer"; public static final String INDEX_YEAR = "Year"; // Literal Strings private static final String ADDING_TO_LIST = "Adding {} to {} list for {}"; private static final String LIT_NAME = "[@name]"; static { categoryMinCountMaster = PropertiesUtil.getIntProperty("mjb.categories.minCount", 3); categoryMaxCountMaster = PropertiesUtil.getIntProperty("mjb.categories.maxCount", 0); movieMaxCountMaster = PropertiesUtil.getIntProperty("mjb.movies.maxCount", 0); minSetCount = PropertiesUtil.getIntProperty("mjb.sets.minSetCount", 2); setsRequireAll = PropertiesUtil.getBooleanProperty("mjb.sets.requireAll", Boolean.FALSE); setsRating = PropertiesUtil.getProperty("mjb.sets.rating", "first"); CATEGORIES_EXPLODE_SET = Arrays .asList(PropertiesUtil.getProperty("mjb.categories.explodeSet", "").split(",")); removeExplodeSet = PropertiesUtil.getBooleanProperty("mjb.categories.explodeSet.removeSet", Boolean.FALSE); keepTVExplodeSet = PropertiesUtil.getBooleanProperty("mjb.categories.explodeSet.keepTV", Boolean.TRUE); beforeSortExplodeSet = PropertiesUtil.getBooleanProperty("mjb.categories.explodeSet.beforeSort", Boolean.FALSE); removeTitleExplodeSet = PropertiesUtil.getBooleanProperty("mjb.categories.explodeSet.removeSet.title", Boolean.FALSE); singleSeriesPage = PropertiesUtil.getBooleanProperty("mjb.singleSeriesPage", Boolean.FALSE); INDEX_LIST = PropertiesUtil.getProperty("mjb.categories.indexList", "Other,Genres,Title,Certification,Year,Library,Set"); String awardTmp = PropertiesUtil.getProperty("mjb.categories.award.events", ""); AWARD_EVENT_LIST = StringTools.isValidString(awardTmp) ? Arrays.asList(awardTmp.split(Movie.SPACE_SLASH_SPACE)) : new ArrayList<String>(); awardTmp = PropertiesUtil.getProperty("mjb.categories.award.name", ""); AWARD_NAME_LIST = StringTools.isValidString(awardTmp) ? Arrays.asList(awardTmp.split(Movie.SPACE_SLASH_SPACE)) : new ArrayList<String>(); awardTmp = PropertiesUtil.getProperty("mjb.categories.award.nominated", ""); AWARD_NOMINATED = StringTools.isValidString(awardTmp) ? Arrays.asList(awardTmp.split(Movie.SPACE_SLASH_SPACE)) : new ArrayList<String>(); awardTmp = PropertiesUtil.getProperty("mjb.categories.award.won", ""); AWARD_WON = StringTools.isValidString(awardTmp) ? Arrays.asList(awardTmp.split(Movie.SPACE_SLASH_SPACE)) : new ArrayList<String>(); scrapeWonAwards = "won".equalsIgnoreCase(PropertiesUtil.getProperty("mjb.scrapeAwards", FALSE)); splitHD = PropertiesUtil.getBooleanProperty("highdef.differentiate", Boolean.FALSE); processExtras = PropertiesUtil.getBooleanProperty("filename.extras.process", Boolean.TRUE); hideWatched = PropertiesUtil.getBooleanProperty("mjb.Library.hideWatched", Boolean.TRUE); ENABLE_WATCH_SCANNER = PropertiesUtil.getBooleanProperty("watched.scanner.enable", Boolean.TRUE); filterGenres = PropertiesUtil.getBooleanProperty("mjb.filter.genres", Boolean.FALSE); fillGenreMap(PropertiesUtil.getProperty("mjb.xmlGenreFile", "genres-default.xml")); FILTER_CERTIFICAION = PropertiesUtil.getBooleanProperty("mjb.filter.certification", Boolean.FALSE); fillCertificationMap(PropertiesUtil.getProperty("mjb.xmlCertificationFile", "certification-default.xml")); maxGenresPerMovie = PropertiesUtil.getIntProperty("genres.max", 3); { String certificationOrder = PropertiesUtil.getProperty("certification.ordering"); if (StringUtils.isNotBlank(certificationOrder)) { for (String cert : certificationOrder.split(",")) { CERTIFICATION_ORDERING.add(cert.trim()); } } } fillCategoryMap(PropertiesUtil.getProperty("mjb.xmlCategoryFile", "categories-default.xml")); charGroupEnglish = PropertiesUtil.getBooleanProperty("indexing.character.groupEnglish", Boolean.FALSE); completePerson = PropertiesUtil.getBooleanProperty("indexing.completePerson", Boolean.TRUE); peopleScan = PropertiesUtil.getBooleanProperty("mjb.people", Boolean.FALSE); peopleExclusive = PropertiesUtil.getBooleanProperty("mjb.people.exclusive", Boolean.FALSE); populateSortOrder(); getNewCategoryProperties(); } public Library() { this.people = new TreeMap<>(); this.indexes = new LinkedHashMap<>(); } /** * Create the sort order for the indexes */ private static void populateSortOrder() { // Compile the sorting comparator list if (SORT_COMP.isEmpty()) { SORT_COMP.add(INDEX_NEW.toLowerCase()); SORT_COMP.add(INDEX_TITLE.toLowerCase()); SORT_COMP.add(INDEX_RATING.toLowerCase()); SORT_COMP.add(INDEX_TOP250.toLowerCase()); SORT_COMP.add(INDEX_YEAR.toLowerCase()); } LOG.debug("Valid sort types are: {}", SORT_COMP.toString()); if (SORT_KEYS.isEmpty()) { setSortProperty(INDEX_PERSON, INDEX_TITLE, Boolean.TRUE); setSortProperty(INDEX_CAST, INDEX_TITLE, Boolean.TRUE); setSortProperty(INDEX_DIRECTOR, INDEX_TITLE, Boolean.TRUE); setSortProperty(INDEX_WRITER, INDEX_TITLE, Boolean.TRUE); setSortProperty(INDEX_RATINGS, INDEX_RATING, Boolean.FALSE); setSortProperty(INDEX_GENRES, INDEX_TITLE, Boolean.TRUE); setSortProperty(INDEX_TITLE, INDEX_TITLE, Boolean.TRUE); setSortProperty(INDEX_CERTIFICATION, INDEX_TITLE, Boolean.TRUE); setSortProperty(INDEX_YEAR, INDEX_TITLE, Boolean.TRUE); setSortProperty(INDEX_LIBRARY, INDEX_TITLE, Boolean.TRUE); setSortProperty(INDEX_COUNTRY, INDEX_TITLE, Boolean.TRUE); setSortProperty(INDEX_AWARD, INDEX_TITLE, Boolean.TRUE); setSortProperty(INDEX_RATING, INDEX_RATING, Boolean.FALSE); setSortProperty(INDEX_HD, INDEX_TITLE, Boolean.TRUE); setSortProperty(INDEX_HD1080, INDEX_TITLE, Boolean.TRUE); setSortProperty(INDEX_HD720, INDEX_TITLE, Boolean.TRUE); setSortProperty(INDEX_3D, INDEX_TITLE, Boolean.TRUE); setSortProperty(INDEX_WATCHED, INDEX_TITLE, Boolean.TRUE); setSortProperty(INDEX_UNWATCHED, INDEX_TITLE, Boolean.TRUE); setSortProperty(INDEX_ALL, INDEX_TITLE, Boolean.TRUE); setSortProperty(INDEX_TVSHOWS, INDEX_TITLE, Boolean.TRUE); setSortProperty(INDEX_MOVIES, INDEX_TITLE, Boolean.TRUE); setSortProperty(INDEX_TOP250, INDEX_TOP250, Boolean.TRUE); // Sort the new categories by descending order setSortProperty(INDEX_NEW, INDEX_NEW, Boolean.FALSE); setSortProperty(INDEX_NEW_MOVIE, INDEX_NEW, Boolean.FALSE); setSortProperty(INDEX_NEW_TV, INDEX_NEW, Boolean.FALSE); } LOG.debug("Library sorting:"); for (Entry<String, String> sk : SORT_KEYS.entrySet()) { LOG.debug(" Category='{}', OrderBy='{}' {}", sk.getKey(), sk.getValue(), SORT_ASC.get(sk.getKey()) ? " (Ascending)" : " (Descending)"); } } /** * Populate the index sorting with the key and the comparator. * * @param indexKey * @param defaultSort * @param defaultOrder */ private static void setSortProperty(String indexKey, String defaultSort, boolean defaultOrder) { String spIndexKey; if (indexKey.contains(" ")) { spIndexKey = indexKey.replaceAll(" ", ""); } else { spIndexKey = indexKey; } spIndexKey = spIndexKey.toLowerCase(); String sortType = PropertiesUtil.getProperty("indexing.sort." + spIndexKey, defaultSort).toLowerCase(); if (StringTools.isNotValidString(sortType) || !SORT_COMP.contains(sortType)) { LOG.warn("Invalid sort type '{}' for category '{}' using default of {}", sortType, spIndexKey, defaultSort); sortType = defaultSort.toLowerCase(); } SORT_KEYS.put(indexKey, sortType); SORT_ASC.put(indexKey, PropertiesUtil.getBooleanProperty("indexing.sort." + spIndexKey + ".asc", defaultOrder)); } /** * Calculate the Movie and TV New category properties */ private static void getNewCategoryProperties() { newMovieDays = PropertiesUtil.getReplacedLongProperty("mjb.newdays.movie", "mjb.newdays", 7); newMovieCount = PropertiesUtil.getReplacedIntProperty("mjb.newcount.movie", "mjb.newcount", 0); newTvDays = PropertiesUtil.getReplacedLongProperty("mjb.newdays.tv", "mjb.newdays", 7); newTvCount = PropertiesUtil.getReplacedIntProperty("mjb.newcount.tv", "mjb.newcount", 0); if (newMovieDays > 0) { LOG.debug("New Movie category will have {} most recent movies in the last {} days", newMovieCount > 0 ? ("the " + newMovieCount) : "all of the", newMovieDays); // Convert newDays from DAYS to MILLISECONDS for comparison purposes newMovieDays = TimeUnit.DAYS.toMillis(newMovieDays); } else { LOG.debug("New Movie category is disabled"); } if (newTvDays > 0) { LOG.debug("New TV category will have {} most recent TV Shows in the last {} days", newTvCount > 0 ? ("the " + newTvCount) : "all of the", newTvDays); // Convert newDays from DAYS to MILLISECONDS for comparison purposes newTvDays = TimeUnit.DAYS.toMillis(newTvDays); } else { LOG.debug("New category is disabled"); } } public static String getMovieKey(IMovieBasicInformation movie) { // Issue 190 StringBuilder key = new StringBuilder(movie.getTitle()); key.append(" (").append(movie.getYear()).append(")"); if (movie.isTVShow()) { // Issue 190 key.append(" Season "); key.append(PADDED_FORMAT.format(movie.getSeason())); } return key.toString().toLowerCase(); } public static List<String> getLibraryOrdering() { return LIBRARY_ORDERING; } // synchronized because scanning can be multi-threaded public synchronized void addMovie(String key, Movie movie) { Movie existingMovie = library.get(key); // logger.debug("Adding video " + key + ", new part: " + (existingMovie != null)); if (movie.isExtra()) { // logger.debug(" It's an extra: " + movie.getBaseName()); extras.put(movie.getBaseName(), movie); } else if (existingMovie == null) { keys.put(movie, key); library.put(key, movie); if (movie.getLibraryDescription().length() > 0 && !LIBRARY_ORDERING.contains(movie.getLibraryDescription())) { LIBRARY_ORDERING.add(movie.getLibraryDescription()); } } else { MovieFile firstMovieFile = movie.getFirstFile(); // Take care of TV-Show (order by episode). Issue 535 - Not sure it's the best place do to this. if (existingMovie.isTVShow() || existingMovie.getMovieFiles().size() > 1) { // The lower episode have to be the main movie. int newEpisodeNumber = firstMovieFile.getFirstPart(); int oldEpisodesFirstNumber = Integer.MAX_VALUE; Collection<MovieFile> espisodesFiles = existingMovie.getFiles(); for (MovieFile movieFile : espisodesFiles) { if (movieFile.getFirstPart() < oldEpisodesFirstNumber) { oldEpisodesFirstNumber = movieFile.getFirstPart(); } } // If the new episode is < than old ( episode 1 < episode 2) if (newEpisodeNumber < oldEpisodesFirstNumber) { // The New episode have to be the 'main' for (MovieFile movieFile : espisodesFiles) { movie.addMovieFile(movieFile); library.put(key, movie); // Replace the old one by the lower. keys.remove(existingMovie); keys.put(movie, key); existingMovie = movie; } } else { // no change existingMovie.addMovieFile(firstMovieFile); } // Issue 2089: cumulate filesize for a TV Show season. existingMovie.setFileSize(movie.getFileSize()); // Set max file date (by calling this method) existingMovie.getLastModifiedTimestamp(); } else { existingMovie.addMovieFile(firstMovieFile); // Update these counters existingMovie.setFileSize(movie.getFileSize()); // Set file date existingMovie.addFileDate(movie.getFileDate()); } } } public void addMovie(Movie movie) { addMovie(getMovieKey(movie), movie); } public void mergeExtras() { for (Map.Entry<String, Movie> extraEntry : extras.entrySet()) { Movie extra = extraEntry.getValue(); // Add extra to the library set library.put(extraEntry.getKey(), extra); // Add the extra to the corresponding movie Movie movie = library.get(getMovieKey(extra)); if (null != movie) { // Extra file added is mark as new. When the XML will be parse // the extra file will be mark as old if it is defined in XML movie.addExtraFile(new ExtraFile(extraEntry.getValue().getFirstFile())); } } } protected static Map<String, Movie> buildIndexMasters(String prefix, Index index) { Map<String, Movie> masters = new HashMap<>(); boolean dirtyInfo = Boolean.FALSE; for (Map.Entry<String, List<Movie>> indexEntry : index.entrySet()) { String indexName = indexEntry.getKey(); List<Movie> indexMovieList = indexEntry.getValue(); // Issue 2098: put to SET information from first movie by order int setIndex = 0; if (!indexMovieList.get(setIndex).isTVShow() && indexMovieList.get(setIndex).getSetOrder(indexName) != null) { int setOrder = indexMovieList.get(setIndex).getSetOrder(indexName); if (setOrder > 1) { for (int i = 1; i < indexMovieList.size(); i++) { if ((indexMovieList.get(i).getSetOrder(indexName) != null) && setOrder > indexMovieList.get(i).getSetOrder(indexName)) { setOrder = indexMovieList.get(i).getSetOrder(indexName); setIndex = i; } } } } // We can't clone the movie because of the Collection objects in there, so we'll have to copy it Movie indexMaster = Movie.newInstance(indexMovieList.get(setIndex)); indexMaster.setSetMaster(true); indexMaster.setSetSize(indexMovieList.size()); indexMaster.setTitle(indexName, indexMaster.getOverrideSource(OverrideFlag.TITLE)); // Overwrite the TitleSort with the indexname only for TV Shows as this will overwrite with changes that are made in a NFO file if (!indexMaster.isTVShow()) { indexMaster.setTitleSort(indexName); } indexMaster.setOriginalTitle(indexName, indexMaster.getOverrideSource(OverrideFlag.ORIGINALTITLE)); indexMaster.setBaseFilename(createPrefix(prefix, createCategoryKey(indexName)) + "1"); indexMaster.setBaseName(makeSafeFilename(indexMaster.getBaseFilename())); // set TV and HD properties of the master int countTV = 0; int countHD = 0; int top250 = -1; String top250source = Movie.UNKNOWN; boolean watched = true; // Assume watched for the check, because any false value will reset it. int maxRating = 0; int sumRating = 0; int currentRating; // Clear any set dirty flags and just use those from the files. indexMaster.clearDirty(); // We Can't use a TreeSet because MF.compareTo just compares part # // so it fails when we combine multiple seasons into one collection Collection<MovieFile> masterMovieFileCollection = new LinkedList<>(); for (Movie movie : indexMovieList) { if (movie.isTVShow()) { countTV++; } if (movie.isHD()) { countHD++; } // If watched is false for any part, then set the master to unwatched watched &= movie.isWatched(); int mTop250 = movie.getTop250(); if (mTop250 > 0 && (top250 < 0 || mTop250 < top250)) { top250 = mTop250; top250source = movie.getOverrideSource(OverrideFlag.TOP250); } currentRating = movie.getRating(); if (currentRating >= 0) { sumRating += currentRating; if (currentRating > maxRating) { maxRating = currentRating; } } Collection<MovieFile> movieFileCollection = movie.getMovieFiles(); if (movieFileCollection != null) { masterMovieFileCollection.addAll(movieFileCollection); } // Update the master fileDate to be the latest of all the members so this indexes correctly in the New category indexMaster.addFileDate(movie.getFileDate()); // Check the dirty status of the movie to see if the set needs to be updated indexMaster.getDirty().addAll(movie.getDirty()); // the dirty info flag is overwritten by dirtyInfo |= movie.isDirty(DirtyFlag.INFO); } indexMaster.setMovieType(countTV > 0 ? Movie.TYPE_TVSHOW : Movie.TYPE_MOVIE); indexMaster.setVideoType(countHD > 0 ? Movie.TYPE_VIDEO_HD : null); indexMaster.setWatchedFile(watched); indexMaster.setTop250(top250, top250source); if ("max".equalsIgnoreCase(setsRating) || ("average".equalsIgnoreCase(setsRating) && (!indexMovieList.isEmpty()))) { Map<String, Integer> ratings = new HashMap<>(); ratings.put("setrating", "max".equalsIgnoreCase(setsRating) ? maxRating : (sumRating / indexMovieList.size())); indexMaster.setRatings(ratings); } indexMaster.setMovieFiles(masterMovieFileCollection); // Keep the current dirty setting indexMaster.setDirty(DirtyFlag.INFO, dirtyInfo); masters.put(indexName, indexMaster); if (LOG.isDebugEnabled()) { StringBuilder sb = new StringBuilder("Setting index master '"); sb.append(indexMaster.getTitle()); sb.append("' - isTV: ").append(indexMaster.isTVShow()); sb.append(" (").append(countTV).append("/").append(indexMovieList.size()).append(")"); sb.append(" - isHD: ").append(indexMaster.isHD()); sb.append(" (").append(countHD).append("/").append(indexMovieList.size()).append(")"); sb.append(" - top250: ").append(indexMaster.getTop250()); sb.append(" - watched: ").append(indexMaster.isWatched()); sb.append(" - rating: ").append(indexMaster.getRating()); sb.append(" - dirty: ").append(indexMaster.showDirty()); LOG.debug(sb.toString()); } } return masters; } protected static void compressSetMovies(List<Movie> movies, Index index, Map<String, Movie> masters, String indexName, String subIndexName) { // Construct an index that includes only the intersection of movies and index Index inMovies = new Index(); for (Map.Entry<String, List<Movie>> indexEntry : index.entrySet()) { for (Movie m : indexEntry.getValue()) { if (movies.contains(m)) { inMovies.addMovie(indexEntry.getKey(), m); } } } // Now, for each list of movies in in_movies, if the list has more than the minSetCount movies // remove them all from the movies list, and insert the corresponding master for (Map.Entry<String, List<Movie>> inMoviesEntry : inMovies.entrySet()) { List<Movie> lm = inMoviesEntry.getValue(); if (lm.size() >= minSetCount && (!setsRequireAll || lm.size() == index.get(inMoviesEntry.getKey()).size())) { boolean tvSet = keepTVExplodeSet && lm.get(0).isTVShow(); boolean explodeSet = CATEGORIES_EXPLODE_SET.contains(indexName) || (indexName.equalsIgnoreCase(INDEX_OTHER) && CATEGORIES_EXPLODE_SET.contains(subIndexName)); if (!beforeSortExplodeSet || !explodeSet || tvSet) { movies.removeAll(lm); } if (!beforeSortExplodeSet || !explodeSet || tvSet || !removeExplodeSet) { movies.add(masters.get(inMoviesEntry.getKey())); } } } } public void buildIndex(ThreadExecutor<Void> tasks) throws Throwable { moviesList.clear(); indexes.clear(); tasks.restart(); final List<Movie> indexMovies = new ArrayList<>(library.values()); moviesList.addAll(library.values()); if (!indexMovies.isEmpty()) { Map<String, Index> dynamicIndexes = new LinkedHashMap<>(); // Add the sets FIRST! That allows users to put series inside sets dynamicIndexes.put(SET, indexBySets(indexMovies)); final Map<String, Index> syncindexes = Collections.synchronizedMap(indexes); for (final String indexStr : INDEX_LIST.split(",")) { tasks.submit(new Callable<Void>() { @Override public Void call() { SystemTools.showMemory(); LOG.info(" Indexing {}...", indexStr); switch (indexStr) { case INDEX_OTHER: syncindexes.put(INDEX_OTHER, indexByProperties(indexMovies)); break; case INDEX_GENRES: syncindexes.put(INDEX_GENRES, indexByGenres(indexMovies)); break; case INDEX_TITLE: syncindexes.put(INDEX_TITLE, indexByTitle(indexMovies)); break; case INDEX_CERTIFICATION: syncindexes.put(INDEX_CERTIFICATION, indexByCertification(indexMovies)); break; case INDEX_YEAR: syncindexes.put(INDEX_YEAR, indexByYear(indexMovies)); break; case INDEX_LIBRARY: syncindexes.put(INDEX_LIBRARY, indexByLibrary(indexMovies)); break; case INDEX_CAST: syncindexes.put(INDEX_CAST, indexByCast(indexMovies)); break; case INDEX_DIRECTOR: syncindexes.put(INDEX_DIRECTOR, indexByDirector(indexMovies)); break; case INDEX_COUNTRY: syncindexes.put(INDEX_COUNTRY, indexByCountry(indexMovies)); break; case INDEX_WRITER: syncindexes.put(INDEX_WRITER, indexByWriter(indexMovies)); break; case INDEX_AWARD: syncindexes.put(INDEX_AWARD, indexByAward(indexMovies)); break; case INDEX_PERSON: syncindexes.put(INDEX_PERSON, indexByPerson(indexMovies)); break; case INDEX_RATINGS: syncindexes.put(INDEX_RATINGS, indexByRatings(indexMovies)); break; default: break; } return null; } }); } tasks.waitFor(); SystemTools.showMemory(); // Make a "copy" of uncompressed index this.keepUncompressedIndexes(); Map<String, Map<String, Movie>> dynamicIndexMasters = new HashMap<>(); for (Map.Entry<String, Index> dynamicEntry : dynamicIndexes.entrySet()) { Map<String, Movie> indexMasters = buildIndexMasters(dynamicEntry.getKey(), dynamicEntry.getValue()); dynamicIndexMasters.put(dynamicEntry.getKey(), indexMasters); for (Map.Entry<String, Index> indexesEntry : indexes.entrySet()) { // For each category in index, compress this one. for (Map.Entry<String, List<Movie>> indexEntry : indexesEntry.getValue().entrySet()) { compressSetMovies(indexEntry.getValue(), dynamicEntry.getValue(), indexMasters, indexesEntry.getKey(), indexEntry.getKey()); } } indexes.put(dynamicEntry.getKey(), dynamicEntry.getValue()); moviesList.addAll(indexMasters.values()); // so the driver knows what's an index master } // Now add the masters to the titles index // Issue 1018 - Check that this index was selected if (INDEX_LIST.contains(INDEX_TITLE)) { for (Map.Entry<String, Map<String, Movie>> dynamicIndexMastersEntry : dynamicIndexMasters .entrySet()) { Index mastersTitlesIndex = indexByTitle(dynamicIndexMastersEntry.getValue().values()); for (Map.Entry<String, List<Movie>> indexEntry : mastersTitlesIndex.entrySet()) { for (Movie m : indexEntry.getValue()) { int setCount = dynamicIndexes.get(dynamicIndexMastersEntry.getKey()).get(m.getTitle()) .size(); if (setCount >= minSetCount) { indexes.get(INDEX_TITLE).addMovie(indexEntry.getKey(), m); } } } } } SystemTools.showMemory(); tasks.restart(); // OK, now that all the index masters are in-place, sort everything. LOG.info(" Sorting Indexes ..."); for (final Map.Entry<String, Index> indexesEntry : indexes.entrySet()) { for (final Map.Entry<String, List<Movie>> indexEntry : indexesEntry.getValue().entrySet()) { tasks.submit(new Callable<Void>() { @Override public Void call() { Comparator<Movie> cmpMovie = getComparator(indexesEntry.getKey(), indexEntry.getKey()); if (cmpMovie == null) { Collections.sort(indexEntry.getValue()); } else { Collections.sort(indexEntry.getValue(), cmpMovie); } return null; } }); } } tasks.waitFor(); SystemTools.showMemory(); // Cut off the Other/New lists if they're too long AND add them to the NEW category if required boolean trimNewTvOK = trimNewCategory(INDEX_NEW_TV, newTvCount); boolean trimNewMovieOK = trimNewCategory(INDEX_NEW_MOVIE, newMovieCount); // Merge the two categories into the Master "New" category if (CATEGORIES_MAP.get(INDEX_NEW) != null) { Index otherIndexes = indexes.get(INDEX_OTHER); List<Movie> newList = new ArrayList<>(); int newMovies = 0; int newTVShows = 0; if (trimNewMovieOK && (CATEGORIES_MAP.get(INDEX_NEW_MOVIE) != null) && (otherIndexes.get(CATEGORIES_MAP.get(INDEX_NEW_MOVIE)) != null)) { newList.addAll(otherIndexes.get(CATEGORIES_MAP.get(INDEX_NEW_MOVIE))); newMovies = otherIndexes.get(CATEGORIES_MAP.get(INDEX_NEW_MOVIE)).size(); } else // Remove the empty "New Movie" category { if (CATEGORIES_MAP.get(INDEX_NEW_MOVIE) != null) { otherIndexes.remove(CATEGORIES_MAP.get(INDEX_NEW_MOVIE)); } } if (trimNewTvOK && (CATEGORIES_MAP.get(INDEX_NEW_TV) != null) && (otherIndexes.get(CATEGORIES_MAP.get(INDEX_NEW_TV)) != null)) { newList.addAll(otherIndexes.get(CATEGORIES_MAP.get(INDEX_NEW_TV))); newTVShows = otherIndexes.get(CATEGORIES_MAP.get(INDEX_NEW_TV)).size(); } else // Remove the empty "New TV" category { if (CATEGORIES_MAP.get(INDEX_NEW_TV) != null) { otherIndexes.remove(CATEGORIES_MAP.get(INDEX_NEW_TV)); } } // If we have new videos, then create the super "New" category if ((newMovies + newTVShows) > 0) { StringBuilder categoryMessage = new StringBuilder("Creating new category with "); if (newMovies > 0) { categoryMessage.append(newMovies).append(" new movie").append(newMovies > 1 ? "s" : ""); } if (newTVShows > 0) { categoryMessage.append(newMovies > 0 ? " & " : ""); categoryMessage.append(newTVShows).append(" new TV Show").append(newTVShows > 1 ? "s" : ""); } LOG.debug(categoryMessage.toString()); otherIndexes.put(CATEGORIES_MAP.get(INDEX_NEW), newList); Collections.sort(otherIndexes.get(CATEGORIES_MAP.get(INDEX_NEW)), new LastModifiedComparator()); } } // Now set up the index masters' posters for (Map.Entry<String, Map<String, Movie>> dynamicIndexMastersEntry : dynamicIndexMasters.entrySet()) { for (Map.Entry<String, Movie> mastersEntry : dynamicIndexMastersEntry.getValue().entrySet()) { List<Movie> set = dynamicIndexes.get(dynamicIndexMastersEntry.getKey()) .get(mastersEntry.getKey()); mastersEntry.getValue().setPosterFilename(set.get(0).getBaseName() + ".jpg"); mastersEntry.getValue().setFile(set.get(0).getFile()); // ensure ArtworkScanner looks in the right directory } } Collections.sort(indexMovies); setMovieListNavigation(indexMovies); SystemTools.showMemory(); } tasks.restart(); final List<Person> indexPersons = new ArrayList<>(people.values()); if (!indexPersons.isEmpty()) { for (final String indexStr : INDEX_LIST.split(",")) { if (!(INDEX_CAST + INDEX_DIRECTOR + INDEX_WRITER + INDEX_PERSON).contains(indexStr)) { continue; } tasks.submit(new Callable<Void>() { @Override public Void call() { SystemTools.showMemory(); LOG.info(" Indexing {} (person)...", indexStr); indexByJob(indexPersons, indexStr.equals(INDEX_CAST) ? Filmography.DEPT_ACTORS : indexStr.equals(INDEX_DIRECTOR) ? Filmography.DEPT_DIRECTING : indexStr.equals(INDEX_WRITER) ? Filmography.DEPT_WRITING : Movie.UNKNOWN, indexStr); return null; } }); } tasks.waitFor(); SystemTools.showMemory(); } } /** * Trim the new category to the required length, add the trimmed video list to the NEW category * * @param catName The name of the category: "New-TV" or "New-Movie" * @param catCount The maximum size of the category * @return */ private boolean trimNewCategory(String catName, int catCount) { boolean trimOK = true; String category = CATEGORIES_MAP.get(catName); //logger.info("Trimming '" + catName + "' ('" + category + "') to " + catCount + " videos"); if (catCount > 0 && category != null) { Index otherIndexes = indexes.get(INDEX_OTHER); if (otherIndexes != null) { List<Movie> newList = otherIndexes.get(category); //logger.info("Current size of '" + catName + "' ('" + category + "') is " + (newList != null ? newList.size() : "NULL")); if ((newList != null) && (newList.size() > catCount)) { newList = newList.subList(0, catCount); otherIndexes.put(category, newList); } } else { LOG.warn("Warning : You need to enable index 'Other' to get '{}' ('{}') category", catName, category); trimOK = false; } } return trimOK; } private void keepUncompressedIndexes() { this.unCompressedIndexes = new HashMap<>(indexes.size()); Set<String> indexeskeySet = this.indexes.keySet(); for (String key : indexeskeySet) { LOG.debug("Copying {} indexes", key); Index index = this.indexes.get(key); Index indexTmp = new Index(); unCompressedIndexes.put(key, indexTmp); for (Map.Entry<String, List<Movie>> keyCategory : index.entrySet()) { List<Movie> listMovie = keyCategory.getValue(); List<Movie> listMovieTmp = new ArrayList<>(listMovie.size()); indexTmp.put(keyCategory.getKey(), listMovieTmp); for (Movie movie : listMovie) { listMovieTmp.add(movie); } } } } private static void setMovieListNavigation(List<Movie> moviesList) { List<Movie> extraList = new ArrayList<>(); IMovieBasicInformation first = null; IMovieBasicInformation last = null; // sort the extras out of the movies for (Movie m : moviesList) { if (m.isExtra()) { extraList.add(m); } else { if (first == null) { // set the first non-extra movie first = m; } // set the last non-extra movie last = m; } } // ignore the extras while sorting the other movies for (int j = 0; j < moviesList.size(); j++) { Movie movie = moviesList.get(j); if (!movie.isExtra()) { movie.setFirst(first == null ? "" : first.getBaseName()); for (int p = j - 1; p >= 0; p--) { Movie prev = moviesList.get(p); if (!prev.isExtra()) { movie.setPrevious(prev.getBaseName()); break; } } for (int n = j + 1; n < moviesList.size(); n++) { Movie next = moviesList.get(n); if (!next.isExtra()) { movie.setNext(next.getBaseName()); break; } } movie.setLast(last == null ? "" : last.getBaseName()); } } // sort the extras separately if (!extraList.isEmpty()) { IMovieBasicInformation firstExtra = extraList.get(0); IMovieBasicInformation lastExtra = extraList.get(extraList.size() - 1); for (int i = 0; i < extraList.size(); i++) { Movie movie = extraList.get(i); movie.setFirst(firstExtra.getBaseName()); movie.setPrevious(i > 0 ? extraList.get(i - 1).getBaseName() : firstExtra.getBaseName()); movie.setNext( i < extraList.size() - 1 ? extraList.get(i + 1).getBaseName() : lastExtra.getBaseName()); movie.setLast(lastExtra.getBaseName()); } } } private static Index indexByTitle(Iterable<Movie> moviesList) { Index index = new Index(); for (Movie movie : moviesList) { if (!movie.isExtra() && (!removeTitleExplodeSet || !movie.isSetMaster())) { String title = movie.getStrippedTitleSort(); if (title.length() > 0) { Character firstCharacter = Character.toUpperCase(title.charAt(0)); if (!Character.isLetter(firstCharacter)) { index.addMovie("09", movie); movie.addIndex(INDEX_TITLE, "09"); } else if (charGroupEnglish && ((firstCharacter >= 'A' && firstCharacter <= 'Z') || (firstCharacter >= 'a' && firstCharacter <= 'z'))) { index.addMovie("AZ", movie); movie.addIndex(INDEX_TITLE, "AZ"); } else { String newChar = StringTools.characterMapReplacement(firstCharacter); index.addMovie(newChar, movie); movie.addIndex(INDEX_TITLE, newChar); } } } } return index; } private static Index indexByYear(Iterable<Movie> moviesList) { Index index = new Index(); for (Movie movie : moviesList) { if (!movie.isExtra()) { String year = getYearCategory(movie.getYear()); if (null != year) { index.addMovie(year, movie); movie.addIndex(INDEX_YEAR, year); } } } return index; } private static Index indexByLibrary(Iterable<Movie> moviesList) { Index index = new Index(); for (Movie movie : moviesList) { if (!movie.isExtra() && movie.getLibraryDescription().length() > 0) { index.addMovie(movie.getLibraryDescription(), movie); movie.addIndex(INDEX_LIBRARY, movie.getLibraryDescription()); } } return index; } private static Index indexByGenres(Iterable<Movie> moviesList) { Index index = new Index(); for (Movie movie : moviesList) { if (!movie.isExtra()) { int cntGenres = 0; for (String genre : movie.getGenres()) { if (cntGenres < maxGenresPerMovie) { index.addMovie(getIndexingGenre(genre), movie); movie.addIndex(INDEX_GENRES, getIndexingGenre(genre)); ++cntGenres; } } } } return index; } private static Index indexByCertification(Iterable<Movie> moviesList) { Index index; if (CERTIFICATION_ORDERING.isEmpty()) { index = new Index(); } else { index = new Index(new CertificationComparator(CERTIFICATION_ORDERING)); } for (Movie movie : moviesList) { if (!movie.isExtra()) { index.addMovie(getIndexingCertification(movie.getCertification()), movie); movie.addIndex(INDEX_CERTIFICATION, getIndexingCertification(movie.getCertification())); } } return index; } /** * Index the videos by the property values This is slightly different from the other indexes as there may be multiple entries * for each of the videos * * @param moviesList * @return */ private static Index indexByProperties(Iterable<Movie> moviesList) { Index index = new Index(); long now = System.currentTimeMillis(); for (Movie movie : moviesList) { if (movie.isExtra()) { // Issue 997: Skip the processing of extras if (processExtras && CATEGORIES_MAP.get(INDEX_EXTRAS) != null) { index.addMovie(CATEGORIES_MAP.get(INDEX_EXTRAS), movie); movie.addIndex(INDEX_EXTRAS, CATEGORIES_MAP.get(INDEX_EXTRAS)); } } else { if (movie.isHD()) { if (splitHD) { // Split the HD category into two categories: HD-720 and HD-1080 if (movie.isHD1080()) { if (CATEGORIES_MAP.get(INDEX_HD1080) != null) { index.addMovie(CATEGORIES_MAP.get(INDEX_HD1080), movie); movie.addIndex(INDEX_HD, CATEGORIES_MAP.get(INDEX_HD1080)); } } else if (CATEGORIES_MAP.get(INDEX_HD720) != null) { index.addMovie(CATEGORIES_MAP.get(INDEX_HD720), movie); movie.addIndex(INDEX_HD, CATEGORIES_MAP.get(INDEX_HD720)); } } else if (CATEGORIES_MAP.get(INDEX_HD) != null) { index.addMovie(CATEGORIES_MAP.get(INDEX_HD), movie); movie.addIndex(INDEX_HD, CATEGORIES_MAP.get(INDEX_HD)); } } if (movie.is3D() && CATEGORIES_MAP.get(INDEX_3D) != null) { index.addMovie(CATEGORIES_MAP.get(INDEX_3D), movie); movie.addIndex(INDEX_3D, CATEGORIES_MAP.get(INDEX_3D)); } if (movie.getTop250() > 0 && CATEGORIES_MAP.get(INDEX_TOP250) != null) { index.addMovie(CATEGORIES_MAP.get(INDEX_TOP250), movie); movie.addIndex(INDEX_TOP250, CATEGORIES_MAP.get(INDEX_TOP250)); } if (movie.getRating() > 0 && CATEGORIES_MAP.get(INDEX_RATING) != null) { index.addMovie(CATEGORIES_MAP.get(INDEX_RATING), movie); movie.addIndex(INDEX_RATING, CATEGORIES_MAP.get(INDEX_RATING)); } if (ENABLE_WATCH_SCANNER) { // Issue 1938 don't create watched/unwatched indexes if scanner is disabled // Add to the Watched or Unwatched category if (movie.isWatched()) { index.addMovie(CATEGORIES_MAP.get(INDEX_WATCHED), movie); movie.addIndex(INDEX_WATCHED, CATEGORIES_MAP.get(INDEX_WATCHED)); } else { index.addMovie(CATEGORIES_MAP.get(INDEX_UNWATCHED), movie); movie.addIndex(INDEX_UNWATCHED, CATEGORIES_MAP.get(INDEX_UNWATCHED)); } } // Add to the New Movie category if (!movie.isTVShow() && (newMovieDays > 0) && (now - movie.getLastModifiedTimestamp() <= newMovieDays) && !(movie.isWatched() && hideWatched && ENABLE_WATCH_SCANNER)) { if (CATEGORIES_MAP.get(INDEX_NEW_MOVIE) != null) { index.addMovie(CATEGORIES_MAP.get(INDEX_NEW_MOVIE), movie); movie.addIndex(INDEX_NEW_MOVIE, CATEGORIES_MAP.get(INDEX_NEW_MOVIE)); } } // Add to the New TV category if (movie.isTVShow() && (newTvDays > 0) && (now - movie.getLastModifiedTimestamp() <= newTvDays) && !(movie.isWatched() && hideWatched && ENABLE_WATCH_SCANNER) && CATEGORIES_MAP.get(INDEX_NEW_TV) != null) { index.addMovie(CATEGORIES_MAP.get(INDEX_NEW_TV), movie); movie.addIndex(INDEX_NEW_TV, CATEGORIES_MAP.get(INDEX_NEW_TV)); } if (CATEGORIES_MAP.get(INDEX_ALL) != null) { index.addMovie(CATEGORIES_MAP.get(INDEX_ALL), movie); movie.addIndex(INDEX_ALL, CATEGORIES_MAP.get(INDEX_ALL)); } if (movie.isTVShow()) { if (CATEGORIES_MAP.get(INDEX_TVSHOWS) != null) { index.addMovie(CATEGORIES_MAP.get(INDEX_TVSHOWS), movie); movie.addIndex(INDEX_TVSHOWS, CATEGORIES_MAP.get(INDEX_TVSHOWS)); } } else if (CATEGORIES_MAP.get(INDEX_MOVIES) != null) { index.addMovie(CATEGORIES_MAP.get(INDEX_MOVIES), movie); movie.addIndex(INDEX_MOVIES, CATEGORIES_MAP.get(INDEX_MOVIES)); } if (!movie.isTVShow() && (!movie.getSetsKeys().isEmpty()) && CATEGORIES_MAP.get(INDEX_SETS) != null) { index.addMovie(CATEGORIES_MAP.get(INDEX_SETS), movie); movie.addIndex(INDEX_SETS, CATEGORIES_MAP.get(INDEX_SETS)); } } } return index; } protected static Index indexBySets(List<Movie> list) { Index index = new Index(false); for (Movie movie : list) { if (!movie.isExtra()) { if (singleSeriesPage && movie.isTVShow()) { index.addMovie(movie.getOriginalTitle(), movie); movie.addIndex(INDEX_SET, movie.getOriginalTitle()); } for (String setKey : movie.getSetsKeys()) { index.addMovie(setKey, movie); movie.addIndex(INDEX_SET, setKey); } } } return index; } protected static Index indexByCast(List<Movie> list) { Index index = new Index(true); for (Movie movie : list) { if (!movie.isExtra()) { if (peopleScan && peopleExclusive) { for (Filmography person : movie.getPeople()) { if (!person.getDepartment().equalsIgnoreCase(Filmography.DEPT_ACTORS) || (completePerson && StringTools.isNotValidString(person.getFilename()))) { continue; } String actor = person.getTitle(); LOG.debug(ADDING_TO_LIST, movie.getTitle(), "cast", actor); index.addMovie(actor, movie); movie.addIndex(INDEX_ACTOR, actor); } } else { for (String actor : movie.getCast()) { LOG.debug(ADDING_TO_LIST, movie.getTitle(), "cast", actor); index.addMovie(actor, movie); movie.addIndex(INDEX_ACTOR, actor); } } } } return index; } protected void indexByJob(List<Person> list, String job, String index) { for (Person person : list) { if ((StringTools.isValidString(job) && !person.getDepartments().contains(job)) || StringTools.isNotValidString(person.getFilename())) { continue; } String actor = person.getTitle(); if (getMovieCountForIndex(index, actor) < calcMinCategoryCount(index)) { continue; } person.addIndex(index, actor); } } /** * Calculate the minimum count for a category based on it's property value. * * @param categoryName * @return */ public static int calcMinCategoryCount(String categoryName) { return calcCategoryCount(categoryName, true, false); } /** * Calculate the maximum count for a category based on it's property value. * * @param categoryName * @return */ public static int calcMaxCategoryCount(String categoryName) { return calcCategoryCount(categoryName, false, false); } /** * Calculate the maximum count for a movie based on it's property value. * * @param categoryName * @return */ public static int calcMaxMovieCount(String categoryName) { return calcCategoryCount(categoryName, false, true); } /** * Calculate the minimum/maximum count for a category/movie based on it's property value. * * @param categoryName * @param getMinimum * @param byMovie * @return */ public static int calcCategoryCount(String categoryName, boolean getMinimum, boolean byMovie) { StringBuilder propertyName = new StringBuilder("mjb."); propertyName.append(byMovie ? "movies." : "categories."); propertyName.append(getMinimum ? "minCount." : "maxCount."); propertyName.append(categoryName); int defaultValue = getMinimum ? categoryMinCountMaster : (byMovie ? movieMaxCountMaster : categoryMaxCountMaster); return PropertiesUtil.getIntProperty(propertyName.toString(), defaultValue); } protected static Index indexByCountry(List<Movie> list) { Index index = new Index(true); for (Movie movie : list) { if (!movie.isExtra()) { for (String country : movie.getCountries()) { index.addMovie(country, movie); movie.addIndex(INDEX_COUNTRY, country); } } } return index; } protected static Index indexByDirector(List<Movie> list) { Index index = new Index(true); for (Movie movie : list) { if (!movie.isExtra()) { if (peopleScan && peopleExclusive) { for (Filmography person : movie.getPeople()) { if (!person.getDepartment().equalsIgnoreCase(Filmography.DEPT_DIRECTING) || (completePerson && StringTools.isNotValidString(person.getFilename()))) { continue; } String director = person.getTitle(); LOG.debug(ADDING_TO_LIST, movie.getTitle(), "director", director); index.addMovie(director, movie); movie.addIndex(INDEX_DIRECTOR, director); } } else { for (String director : movie.getDirectors()) { LOG.debug(ADDING_TO_LIST, movie.getTitle(), "director", director); index.addMovie(director, movie); movie.addIndex(INDEX_DIRECTOR, director); } } } } return index; } protected static Index indexByWriter(List<Movie> list) { Index index = new Index(true); for (Movie movie : list) { if (!movie.isExtra()) { if (peopleScan && peopleExclusive) { for (Filmography person : movie.getPeople()) { if (!person.getDepartment().equalsIgnoreCase(Filmography.DEPT_WRITING) || (completePerson && StringTools.isNotValidString(person.getFilename()))) { continue; } String writer = person.getTitle(); LOG.debug(ADDING_TO_LIST, movie.getTitle(), "writer", writer); index.addMovie(writer, movie); movie.addIndex(INDEX_WRITER, writer); } } else { for (String writer : movie.getWriters()) { LOG.debug(ADDING_TO_LIST, movie.getTitle(), "writer", writer); index.addMovie(writer, movie); movie.addIndex(INDEX_WRITER, writer); } } } } return index; } protected static Index indexByAward(List<Movie> list) { Index index = new Index(true); for (Movie movie : list) { if (!movie.isExtra()) { for (AwardEvent awardEvent : movie.getAwards()) { String awardName = awardEvent.getName(); boolean found = AWARD_EVENT_LIST.isEmpty() && AWARD_NAME_LIST.isEmpty(); if (found || AWARD_EVENT_LIST.contains(awardName) || !AWARD_NAME_LIST.isEmpty() && !found) { for (Award award : awardEvent.getAwards()) { if (AWARD_NAME_LIST.isEmpty() || AWARD_NAME_LIST.contains(award.getName())) { int flag = (scrapeWonAwards ? 0 : ((AWARD_NOMINATED.isEmpty() ? 0 : 8) + (award.getNominations().isEmpty() ? 0 : 4))) + (AWARD_WON.isEmpty() ? 0 : 2) + (award.getWons().isEmpty() ? 0 : 1); found = "145".contains(Integer.toString(flag)); if (!found && (flag > 10)) { for (String nomination : award.getNominations()) { found = AWARD_NOMINATED.contains(nomination); if (found) { break; } } } if (!found && !AWARD_WON.isEmpty() && !award.getWons().isEmpty()) { for (String nomination : award.getWons()) { found = AWARD_WON.contains(nomination); if (found) { break; } } } } if (found) { break; } } } if (found) { LOG.debug(ADDING_TO_LIST, movie.getTitle(), "award", awardName); index.addMovie(awardName, movie); movie.addIndex(INDEX_AWARD, awardName); } } } } return index; } protected static Index indexByPerson(List<Movie> list) { Index index = new Index(true); for (Movie movie : list) { if (movie.isExtra()) { continue; } for (Filmography person : movie.getPeople()) { if (completePerson && StringTools.isNotValidString(person.getFilename())) { continue; } String name = person.getName(); LOG.debug(ADDING_TO_LIST, movie.getTitle(), "person", name); index.addMovie(name, movie); movie.addIndex(INDEX_PERSON, name); } } return index; } protected static Index indexByRatings(List<Movie> list) { LOG.info("INDEXING: Ratings"); Index index = new Index(true); for (Movie movie : list) { if (!movie.isExtra() && (movie.getRating() > 0)) { String sRating = Double.toString(Math.floor((double) movie.getRating() / (double) 10)).replace(".0", ""); // Convert and remove the ".0" sRating = sRating + ".0-" + sRating + ".9"; LOG.debug(ADDING_TO_LIST, movie.getTitle(), "ratings", sRating); index.addMovie(sRating, movie); movie.addIndex(INDEX_RATINGS, sRating); } } return index; } public int getMovieCountForIndex(String indexName, String category) { Index index = unCompressedIndexes.get(indexName); if (index == null) { index = indexes.get(indexName); } if (index == null) { return -1; } List<Movie> categoryList = index.get(category); if (categoryList != null) { return categoryList.size(); } return -1; } /** * Checks if there is a master (will be shown in the index) genre for the specified one. * * @param genre Genre to find the master for * @return Genre itself or master if available. */ public static String getIndexingGenre(String genre) { if (!filterGenres) { return genre; } String masterGenre = GENRES_MAP.get(genre); if (masterGenre != null) { return masterGenre; } return genre; } /** * Checks if there is a master (will be shown in the index) Certification for the specified one. * * @param certification Certification to find the master for * @return Certification itself or master if available. */ public static String getIndexingCertification(String certification) { if (!FILTER_CERTIFICAION) { return certification; } String masterCertification = CERTIFICATIONS_MAP.get(certification); if (StringUtils.isNotBlank(masterCertification)) { return masterCertification; } else if (StringTools.isValidString(defaultCertification)) { return defaultCertification; } else { return certification; } } @Override public void clear() { library.clear(); people.clear(); } @Override public boolean containsKey(Object key) { return library.containsKey(key); } @Override public boolean containsValue(Object value) { return library.containsValue(value); } @Override public Set<Entry<String, Movie>> entrySet() { return library.entrySet(); } @Override public Movie get(Object key) { return library.get(key); } @Override public boolean isEmpty() { return library.isEmpty(); } @Override public Set<String> keySet() { return library.keySet(); } @Override public Movie put(String key, Movie value) { return library.put(key, value); } @Override public void putAll(Map<? extends String, ? extends Movie> m) { library.putAll(m); } @Override public Movie remove(Object key) { Movie m = library.remove(key); if (m != null) { keys.remove(m); } return m; } public Movie remove(Movie m) { String key = keys.get(m); return library.remove(key); } @Override public int size() { return library.size(); } @Override public String toString() { return library.toString(); } @Override public List<Movie> values() { return new ArrayList<>(library.values()); } public List<Movie> getMoviesList() { return moviesList; } public void setMoviesList(List<Movie> moviesList) { this.moviesList = moviesList; } public List<Movie> getMoviesByIndexKey(String key) { for (Map<String, List<Movie>> index : indexes.values()) { List<Movie> movies = index.get(key); if (movies != null) { return movies; } } return new ArrayList<>(); } public Map<String, Index> getIndexes() { return indexes; } private static void fillGenreMap(String xmlGenreFilename) { File xmlGenreFile = new File(xmlGenreFilename); if (xmlGenreFile.exists() && xmlGenreFile.isFile() && xmlGenreFilename.toUpperCase().endsWith("XML")) { try { XMLConfiguration c = new XMLConfiguration(xmlGenreFile); List<HierarchicalConfiguration> genres = c.configurationsAt("genre"); for (HierarchicalConfiguration genre : genres) { String masterGenre = genre.getString(LIT_NAME); LOG.trace("New masterGenre parsed : (" + masterGenre + ")"); List<Object> subgenres = genre.getList("subgenre"); for (Object subgenre : subgenres) { LOG.trace("New genre added to map : (" + subgenre + "," + masterGenre + ")"); GENRES_MAP.put((String) subgenre, masterGenre); } } } catch (ConfigurationException error) { LOG.error("Failed parsing moviejukebox genre input file: {}", xmlGenreFile.getName()); LOG.error(SystemTools.getStackTrace(error)); } } else { LOG.error("The moviejukebox genre input file you specified is invalid: {}", xmlGenreFile.getName()); } } private static void fillCertificationMap(String xmlCertificationFilename) { File xmlCertificationFile = new File(xmlCertificationFilename); if (xmlCertificationFile.exists() && xmlCertificationFile.isFile() && xmlCertificationFilename.toUpperCase().endsWith("XML")) { try { XMLConfiguration conf = new XMLConfiguration(xmlCertificationFile); List<HierarchicalConfiguration> certifications = conf.configurationsAt("certification"); for (HierarchicalConfiguration certification : certifications) { String masterCertification = certification.getString(LIT_NAME); List<Object> subcertifications = certification.getList("subcertification"); for (Object subcertification : subcertifications) { CERTIFICATIONS_MAP.put((String) subcertification, masterCertification); } } if (conf.containsKey("default")) { defaultCertification = conf.getString("default"); LOG.info("Found default certification: {}", defaultCertification); } } catch (ConfigurationException error) { LOG.error("Failed parsing moviejukebox certification input file: {}", xmlCertificationFile.getName()); LOG.error(SystemTools.getStackTrace(error)); } } else { LOG.error("The moviejukebox certification input file you specified is invalid: {}", xmlCertificationFile.getName()); } } private static void fillCategoryMap(String xmlCategoryFilename) { File xmlFile = new File(xmlCategoryFilename); if (xmlFile.exists() && xmlFile.isFile() && xmlCategoryFilename.toUpperCase().endsWith("XML")) { try { XMLConfiguration c = new XMLConfiguration(xmlFile); List<HierarchicalConfiguration> categories = c.configurationsAt("category"); for (HierarchicalConfiguration category : categories) { boolean enabled = Boolean.parseBoolean(category.getString("enable", TRUE)); if (enabled) { String origName = category.getString(LIT_NAME); String newName = category.getString("rename", origName); CATEGORIES_MAP.put(origName, newName); //logger.debug("Added category '" + origName + "' with name '" + newName + "'"); } } } catch (ConfigurationException error) { LOG.error("Failed parsing moviejukebox category input file: {}", xmlFile.getName()); LOG.error(SystemTools.getStackTrace(error)); } } else { LOG.error("The moviejukebox category input file you specified is invalid: {}", xmlFile.getName()); } } public Map<String, String> getCategoriesMap() { return CATEGORIES_MAP; } /** * Find the first category in the first index that has any movies in it For Issue 436 * * @return */ public String getDefaultCategory() { for (Index index : indexes.values()) { for (String cat : CATEGORIES_MAP.values()) { if (index.containsKey(cat) && !index.get(cat).isEmpty()) { return cat; } } } return null; } protected static Comparator<Movie> getComparator(String category, String key) { Comparator<Movie> cmpMovie = null; String originalKey = getOriginalCategory(key, Boolean.TRUE); switch (category) { case SET: cmpMovie = new MovieSetComparator(key); break; case INDEX_OTHER: if (key.equals(CATEGORIES_MAP.get(INDEX_NEW)) || key.equals(CATEGORIES_MAP.get(INDEX_NEW_TV)) || key.equals(CATEGORIES_MAP.get(INDEX_NEW_MOVIE))) { cmpMovie = new LastModifiedComparator(SORT_ASC.get(originalKey)); } else if (key.equals(CATEGORIES_MAP.get(INDEX_TOP250))) { cmpMovie = new MovieTop250Comparator(SORT_ASC.get(INDEX_TOP250)); } else if (key.equals(CATEGORIES_MAP.get(INDEX_ALL))) { cmpMovie = getComparator(INDEX_ALL); } else if (key.equals(CATEGORIES_MAP.get(INDEX_TVSHOWS))) { cmpMovie = getComparator(INDEX_TVSHOWS); } else if (key.equals(CATEGORIES_MAP.get(INDEX_MOVIES))) { cmpMovie = getComparator(INDEX_MOVIES); } else if (key.equals(CATEGORIES_MAP.get(INDEX_WATCHED))) { cmpMovie = getComparator(INDEX_WATCHED); } else if (key.equals(CATEGORIES_MAP.get(INDEX_UNWATCHED))) { cmpMovie = getComparator(INDEX_UNWATCHED); } else if (key.equals(CATEGORIES_MAP.get(INDEX_RATING))) { cmpMovie = getComparator(INDEX_RATING); } else if (key.equals(CATEGORIES_MAP.get(INDEX_HD))) { cmpMovie = getComparator(INDEX_HD); } else if (key.equals(CATEGORIES_MAP.get(INDEX_HD1080))) { cmpMovie = getComparator(INDEX_HD1080); } else if (key.equals(CATEGORIES_MAP.get(INDEX_HD720))) { cmpMovie = getComparator(INDEX_HD720); } else if (key.equals(CATEGORIES_MAP.get(INDEX_3D))) { cmpMovie = getComparator(INDEX_3D); } break; default: cmpMovie = getComparator(category); break; } return cmpMovie; } /** * Get the comparator for the category. * * @param category * @return */ private static Comparator<Movie> getComparator(String category) { Comparator<Movie> cmpMovie = null; String sortKey = SORT_KEYS.get(category); boolean ascending = SORT_ASC.get(category); if (StringTools.isNotValidString(sortKey)) { return cmpMovie; } if (sortKey.equalsIgnoreCase(INDEX_NEW)) { cmpMovie = new LastModifiedComparator(ascending); } else if (sortKey.equalsIgnoreCase(INDEX_TITLE)) { cmpMovie = new MovieTitleComparator(ascending); } else if (sortKey.equalsIgnoreCase(INDEX_RATING)) { cmpMovie = new MovieRatingComparator(ascending); } else if (sortKey.equalsIgnoreCase(INDEX_TOP250)) { cmpMovie = new MovieTop250Comparator(ascending); } else if (sortKey.equalsIgnoreCase(INDEX_YEAR)) { cmpMovie = new MovieReleaseComparator(ascending); } return cmpMovie; } /** * Find the un-modified category name. * * The Category name could be changed by the use of the Category XML file. * * This function will return the original, unchanged name * * @param newCategory * @param returnCategory * @return */ public static String getOriginalCategory(String newCategory, boolean returnCategory) { for (Map.Entry<String, String> singleCategory : CATEGORIES_MAP.entrySet()) { if (singleCategory.getValue().equals(newCategory)) { return singleCategory.getKey(); } } // Check to see if we should return the original category that was passed if (returnCategory) { return newCategory; } return Movie.UNKNOWN; } /** * Find the renamed category name from the original name * * The Category name could be changed by the use of the Category XML file. * * This function will return the new name. * * @param newCategory * @return */ public static String getRenamedCategory(String newCategory) { return CATEGORIES_MAP.get(newCategory); } public static boolean isFilterGenres() { return filterGenres; } public static void setFilterGenres(boolean filterGenres) { Library.filterGenres = filterGenres; } public static boolean isSingleSeriesPage() { return singleSeriesPage; } public static void setSingleSeriesPage(boolean singleSeriesPage) { Library.singleSeriesPage = singleSeriesPage; } public static Collection<String> getPrefixes() { return Arrays.asList(new String[] { INDEX_OTHER.toUpperCase(), INDEX_CERTIFICATION.toUpperCase(), INDEX_TITLE.toUpperCase(), INDEX_YEAR.toUpperCase(), INDEX_GENRES.toUpperCase(), INDEX_SET.toUpperCase(), INDEX_LIBRARY.toUpperCase(), INDEX_CAST.toUpperCase(), INDEX_DIRECTOR.toUpperCase(), INDEX_COUNTRY.toUpperCase(), INDEX_CATEGORIES.toUpperCase(), INDEX_AWARD.toUpperCase(), INDEX_PERSON.toUpperCase(), INDEX_RATINGS.toUpperCase() }); } /** * Determine the year banding for the category. * * If the year is this year or last year, return those, otherwise return the decade the year resides in * * @param filmYear The year to check * @return "This Year", "Last Year" or the decade range (1990-1999) */ public static String getYearCategory(final String filmYear) { StringBuilder yearCat; if (StringTools.isValidString(filmYear) && StringUtils.isNumeric(filmYear)) { if (filmYear.equals(String.valueOf(CURRENT_YEAR))) { yearCat = new StringBuilder("This Year"); } else if (filmYear.equals(String.valueOf(CURRENT_YEAR - 1))) { yearCat = new StringBuilder("Last Year"); } else { String beginYear = filmYear.substring(0, filmYear.length() - 1) + "0"; String endYear; int tmpYear = NumberUtils.toInt(filmYear, -1); if (tmpYear < 0) { LOG.debug("Year is not number: {}", filmYear); return Movie.UNKNOWN; } else if (tmpYear >= CURRENT_DECADE) { // The film year is in the current decade, so we need to adjust the end year endYear = String.valueOf(FINAL_YEAR); } else { // Otherwise it's 9 endYear = filmYear.substring(0, filmYear.length() - 1) + "9"; } LOG.trace("Library years for categories: Begin='{}' End='{}'", beginYear, endYear); yearCat = new StringBuilder(beginYear); yearCat.append("-").append(endYear.substring(endYear.length() >= 4 ? endYear.length() - 2 : 0)); } } else { LOG.trace("Library: Invalid year '{}'", filmYear); yearCat = new StringBuilder(Movie.UNKNOWN); } return yearCat.toString(); } public List<Movie> getMatchingMoviesList(String indexName, List<Movie> boxedSetMovies, String categorie) { List<Movie> response = new ArrayList<>(); List<Movie> list = this.unCompressedIndexes.get(indexName).get(categorie); if (list == null) { return response; } for (Movie movie : boxedSetMovies) { if (list.contains(movie)) { LOG.debug("Movie {} match for {}[{}]", movie.getTitle(), indexName, categorie); response.add(movie); } } return response; } public void addGeneratedIndex(IndexInfo index) { generatedIndexes.add(index); } public Collection<IndexInfo> getGeneratedIndexes() { return generatedIndexes; } public static String getPersonKey(Person person) { String key = person.getName() + "/" + person.getId(); key = key.toLowerCase(); return key; } public void addPerson(String key, Person person) { if (person != null) { Person existingPerson = getPerson(key); if (existingPerson == null) { people.put(key, person); } } } public void addPerson(Person person) { addPerson(getPersonKey(person), person); } public Collection<Person> getPeople() { return people.values(); } public void setPeople(Collection<Person> people) { people.clear(); for (Person person : people) { addPerson(person); } } public Person getPerson(String key) { return people.get(key); } public Person getPerson(Person person) { return people.get(getPersonKey(person)); } public Person getPersonByName(String name) { for (Person person : people.values()) { if (person.getName().equalsIgnoreCase(name)) { return person; } } return null; } public boolean isDirty() { return isDirty; } public void setDirty() { isDirty = true; } public void setDirty(boolean dirty) { isDirty = dirty; } public void toggleDirty(boolean dirty) { isDirty |= dirty; } public void addDirtyLibrary(String name) { if (StringTools.isValidString(name) && !DIRTY_LIBRARIES.contains(name)) { DIRTY_LIBRARIES.add(name); setDirty(); } } public boolean isDirtyLibrary(String name) { return StringTools.isValidString(name) && DIRTY_LIBRARIES.contains(name); } }