package com.gmail.dpierron.calibre.opds; /** * Abstract class that contains methods that are common to all * types of tag subcatalog. */ import com.gmail.dpierron.calibre.configuration.Icons; import com.gmail.dpierron.calibre.configuration.ConfigurationManager; import com.gmail.dpierron.calibre.datamodel.Book; import com.gmail.dpierron.calibre.datamodel.Tag; import com.gmail.dpierron.calibre.datamodel.filter.FilterHelper; import com.gmail.dpierron.calibre.datamodel.filter.RemoveSelectedTagsFilter; import com.gmail.dpierron.tools.i18n.Localization; import com.gmail.dpierron.tools.Helper; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.jdom2.Element; import java.io.IOException; import java.text.Collator; import java.util.*; public abstract class TagsSubCatalog extends BooksSubCatalog { private final static Logger logger = LogManager.getLogger(TagsSubCatalog.class); private final static Collator collator = Collator.getInstance(ConfigurationManager.getLocale()); private List<Tag> tags; private Map<Tag, List<Book>> mapOfBooksByTag; public TagsSubCatalog(List<Object> stuffToFilterOut, List<Book> books) { super(stuffToFilterOut, books); } public TagsSubCatalog(List<Book> books) { super(books); } /** * * @param originalBooks The list of books to check * @return The list of books passing the criteria (may be an empty list if none found) */ @Override List<Book> filterOutStuff(List<Book> originalBooks) { List<Book> result = originalBooks; Set<Tag> tagsToRemove = new TreeSet<Tag>(); for (Object objectToFilterOut : getStuffToFilterOut()) { if (objectToFilterOut instanceof Tag) tagsToRemove.add((Tag) objectToFilterOut); } if (Helper.isNotNullOrEmpty(tagsToRemove)) { // make a copy of the books because RemoveSelectedTagsFilter actually removes tags from the Book objects result = new LinkedList<Book>(); for (Book originalBook : originalBooks) { Book newBook = originalBook.copy(); result.add(newBook); } result = FilterHelper.filter(new RemoveSelectedTagsFilter(tagsToRemove), result); } return result; } /** * Get the list of tags we want to use * @return */ List<Tag> getTags() { if (tags == null) { tags = new LinkedList<Tag>(); for (Book book : getBooks()) { for (Tag tag : book.getTags()) { if (! CatalogManager.getTagsToIgnore().contains(tag) && ! tags.contains(tag)) { tags.add(tag); } } } } // sort the tags alphabetically Collections.sort(tags, new Comparator<Tag>() { public int compare(Tag o1, Tag o2) { return Helper.checkedCollatorCompareIgnoreCase(o1 == null ? "" : o1.getName(), o2.getName()); } }); return tags; } public Map<Tag, List<Book>> getMapOfBooksByTag() { if (mapOfBooksByTag == null) { mapOfBooksByTag = new HashMap<Tag, List<Book>>(); for (Book book : getBooks()) { for (Tag tag : book.getTags()) { List<Book> books = mapOfBooksByTag.get(tag); if (books == null) { books = new LinkedList<Book>(); mapOfBooksByTag.put(tag, books); } books.add(book); } } } return mapOfBooksByTag; } /** * * @param tag * @param books * @return */ private boolean makeTagDeep(Tag tag, List<Book> books) { if (tag == null) return false; if (Helper.isNullOrEmpty(tag.getName())) return false; if (books.size() < currentProfile.getMinBooksToMakeDeepLevel()) return false; String name = tag.getName().toUpperCase(Locale.ENGLISH); for (String tagToMakeDeep : currentProfile.getTokenizedTagsToMakeDeep()) { if (tagToMakeDeep.contains("*")) { tagToMakeDeep = tagToMakeDeep.substring(0, tagToMakeDeep.indexOf('*')); if (name.startsWith(tagToMakeDeep)) return true; } else { if (name.equals(tagToMakeDeep.toUpperCase(Locale.ENGLISH))) return true; } } return false; } static void sortBooksByAuthorAndTitle(List<Book> books) { Collections.sort(books, new Comparator<Book>() { public int compare(Book o1, Book o2) { String s1 = o1.getAuthorSort(); String s2 = o2.getAuthorSort(); if (! s1.equals(s2)) { return Helper.checkedCollatorCompareIgnoreCase(s1, s2, collator); } // If authors equal compare on title. return Helper.checkedCollatorCompareIgnoreCase(o1.getTitleToSplitByLetter(), o2.getTitleToSplitByLetter(), collator); } }); } /** * Get the base filename that is used to store a given author * * Since we always hold a full list of authors at the top level the * name can be derived purely knowing the author involved. * @param tag * @return */ public static String getTagFolderFilenameNoLevel(Tag tag) { return getCatalogBaseFolderFileNameIdNoLevelSplit(Constants.TAG_TYPE,tag.getId(), 100); } /** * Get the base filename that is used to store a given author * This version works within the given level * @param tag * @return */ public String getTagFolderFilenameWithLevel (Tag tag) { if (currentProfile.getDontSplitTagsOn()) { return getCatalogBaseFolderFileNameIdSplit(Constants.TAG_TYPE, tag.getId(), 100); } else { return getCatalogBaseFolderFileNameIdSplit(Constants.TAG_TYPE, tag.getId(), 100); } } /** * Get the details for a specific tag * * @param pBreadcrumbs * @param tagObject * @param opts baseurn * titleWhenCategorized * @return * @throws IOException */ // public Element getTagEntry(Breadcrumbs pBreadcrumbs, Tag tag, String baseurn, String titleWhenCategorized) throws IOException { public Element getDetailedEntry(Breadcrumbs pBreadcrumbs, Object tagObject, Object... opts) throws IOException { assert pBreadcrumbs != null; assert tagObject != null && tagObject.getClass().equals(Tag.class); Tag tag = (Tag)tagObject; assert opts[0] != null && opts[0].getClass().equals(String.class); String baseurn = (String)opts[0]; if (opts[1] != null) assert opts[1].getClass().equals(String.class); String titleWhenCategorized = (String)opts[1]; if (logger.isDebugEnabled()) logger.debug("getObjectEntry: Entry (" + pBreadcrumbs + "/" + tag + ")"); tag.setDone(); CatalogManager.callback.showMessage(pBreadcrumbs.toString()); if (!isInDeepLevel()) { CatalogManager.callback.incStepProgressIndicatorPosition(); } List<Book> books = getMapOfBooksByTag().get(tag); if (Helper.isNullOrEmpty(books)) { if (logger.isDebugEnabled()) logger.debug("getObjectEntry: Exit (no books fond for tag"); return null; } // Tags are held at each level (i.e. not the top level) String filename = getTagFolderFilenameWithLevel(tag); String title = (titleWhenCategorized != null ? titleWhenCategorized : tag.getName()); String urn = baseurn + Constants.URN_SEPARATOR + tag.getId(); // Sort books according to user requirements if (logger.isDebugEnabled()) logger.debug("sorting " + books.size() + " books" + (currentProfile.getSortTagsByAuthor() ? " (withon author)" : "")); if (currentProfile.getSortTagsByAuthor()) { // #c2o-212 Sort tag books by author and then title sortBooksByAuthorAndTitle(books); } else { // sort books by title only sortBooksByTitle(books); } SplitOption splitOption = maxSplitLevels > 0 ? SplitOption.SplitByLetter : SplitOption.Paginate; // check if we need to make this tag deep if (makeTagDeep(tag, books)) { // specify that this is a deep level String summary = Localization.Main.getText("deeplevel.summary", Summarizer.getBookWord(books.size())); if (logger.isDebugEnabled()) { logger.debug("getObjectEntry: Making a deep level for tag " + tag); logger.trace("getObjectEntry: Breadcrumbs=" + pBreadcrumbs.toString()); } // String urlExt = optimizeCatalogURL(catalogManager.getCatalogFileUrl(filename + Constants.XML_EXTENSION, true)); // Breadcrumbs breadcrumbs = Breadcrumbs.addBreadcrumb(pBreadcrumbs, tag.getName(), null); LevelSubCatalog level = new LevelSubCatalog(books, title); level.setCatalogLevel(Breadcrumbs.addBreadcrumb(pBreadcrumbs, tag.getName(), null)); // Create a brand new level using current breadcrumbs! level.setCatalogType(""); level.setCatalogFolder(""); level.setCatalogBaseFilename(filename); return level.getCatalog(pBreadcrumbs, getStuffToFilterOutAnd(tag), // Used to determine what's left! true, // Always in sub-dir for a tag ! summary, urn, splitOption, useExternalIcons ? getIconPrefix(true) + Icons.ICONFILE_TAGS : Icons.ICON_TAGS); } else { // try and list the items to make the summary String summary = Summarizer.summarizeBooks(books); if (logger.isDebugEnabled()) { logger.debug("getObjectEntry: making a simple book list for tag " + tag); if (logger.isTraceEnabled()) logger.trace("getObjectEntry: Breadcrumbs=" + pBreadcrumbs.toString()); } if (currentProfile.getTagBooksNoSplit() && splitOption == SplitOption.SplitByLetter) { splitOption = SplitOption.Paginate; } Element element = getListOfBooks(pBreadcrumbs, books, true, // Always in sub-dir for tag 0, title, summary, urn, filename, splitOption, useExternalIcons ? getIconPrefix(true) + Icons.ICONFILE_TAGS : Icons.ICON_TAGS, null); if (logger.isDebugEnabled()) logger.debug("getObjectEntry: Exit"); return element; } } // abstract Composite<Element, String> getCatalog(Breadcrumbs pBreadcrumbs, boolean inSubDir) throws IOException; abstract Element getCatalog(Breadcrumbs pBreadcrumbs, boolean inSubDir) throws IOException; }