package com.gmail.dpierron.calibre.opds; /** * Class for defining a new tree set of levels based on a tag, * with tags possibly split by a defined character. This is * a way f implementing what are known as hierarchical tags * in Calibre. */ import com.gmail.dpierron.calibre.configuration.Icons; import com.gmail.dpierron.calibre.datamodel.Book; import com.gmail.dpierron.calibre.datamodel.Tag; import com.gmail.dpierron.tools.i18n.Localization; import com.gmail.dpierron.calibre.trook.TrookSpecificSearchDatabaseManager; import com.gmail.dpierron.tools.Helper; import com.gmail.dpierron.tools.RootTreeNode; import com.gmail.dpierron.tools.TreeNode; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.jdom2.Element; import java.io.IOException; import java.util.LinkedList; import java.util.List; public class TagTreeSubCatalog extends TagsSubCatalog { private final static Logger logger = LogManager.getLogger(TagTreeSubCatalog.class); // CONSTRUCTOR(S) public TagTreeSubCatalog(List<Object> stuffToFilterOut, List<Book> books) { super(stuffToFilterOut, books); setCatalogType(Constants.TAGTREE_TYPE); } public TagTreeSubCatalog(List<Book> books) { super(books); setCatalogType(Constants.TAGTREE_TYPE); } // METHODS /** * Generate a tag tree for the current level * * The actual tag associated with a node is stored * as data information * * @param pBreadcrumbs * @return * @throws IOException */ @Override Element getCatalog(Breadcrumbs pBreadcrumbs, boolean inSubDir) throws IOException { Element result; if (logger.isDebugEnabled()) logger.debug("getCatalog: pBreadcrumbs=" + pBreadcrumbs.toString() + ", inSubDir=" + inSubDir); String splitTagsOn = currentProfile.getSplitTagsOn(); assert Helper.isNotNullOrEmpty(splitTagsOn); TreeNode root = new RootTreeNode(); // Work through the tags creating the tree if (logger.isTraceEnabled()) logger.trace("generate initial tree"); for (Tag tag : getTags()) { String[] partsOfTag = tag.getPartsOfTag(splitTagsOn); TreeNode currentPositionInTree = root; for (int i = 0; i < partsOfTag.length; i++) { String partOfTag = partsOfTag[i]; TreeNode nextPositionInTree = currentPositionInTree.getChildWithId(partOfTag); if (nextPositionInTree == null) { nextPositionInTree = new TreeNode(partOfTag); currentPositionInTree.addChild(nextPositionInTree); currentPositionInTree = nextPositionInTree; } else currentPositionInTree = nextPositionInTree; } // Mark the tag this node uses currentPositionInTree.setData(tag); } // browse the tree, removing unneeded levels (single childs up to the leafs) if (logger.isTraceEnabled()) logger.trace("remove unneeded levels"); removeUnNeededLevelsInTree(root, null); // Now get the resulting page set result = getLevelOfTreeNode(pBreadcrumbs, root); if (logger.isDebugEnabled()) logger.debug("getCatalog: exit (pBreadcrumbs=" + pBreadcrumbs.toString() + ")"); return result; } /** * Trim un-needed nodes from the tree. * * We assume that any node that only has a single * child can effectively have the child collapsed * into the parent node. This will stop us generating * a series of pages that only have a single entry. * * NOTE: It is written as a free-standing routine so it cn be called recursively. * * @param node * @param removedParent * @return */ private TreeNode removeUnNeededLevelsInTree(TreeNode node, TreeNode removedParent) { // if (logger.isTraceEnabled()) logger.trace("removeUnNeededLevel: node=" + node + ", removedParent=" + removedParent); if (removedParent != null) { node.setId(removedParent.getId() + currentProfile.getSplitTagsOn() + node.getId()); } if (node.getData() != null) { // this is a leaf return node; } List<TreeNode> newChildren = new LinkedList<TreeNode>(); for (TreeNode childNode : node.getChildren()) { if (childNode.getData() == null && childNode.getChildren().size() <= 1) { if (childNode.getChildren().size() == 0) { // useless node // TODO: ITIMPI: Feel there should be something done here if this condition can really ever occur int dummy = 1; // TODO See if we really ever get here! } else { // useless level so remove it TreeNode newChild = removeUnNeededLevelsInTree(childNode.getChildren().get(0), childNode); if (newChild != null) { newChild.setParent(node); newChildren.add(newChild); } } } else { newChildren.add(removeUnNeededLevelsInTree(childNode, null)); } } node.setChildren(newChildren); return node; } /** * Initial entry point to creating a tree list of tags * * @param pBreadcrumbs * @param level * @return * @throws IOException */ private Element getLevelOfTreeNode(Breadcrumbs pBreadcrumbs, TreeNode level) throws IOException { if (logger.isDebugEnabled()) logger.debug("getLevelOfTreeNode: pBreadcrumbs=" + pBreadcrumbs + ", level=" + level); Element result; if (Helper.isNullOrEmpty(level.getChildren())) { Tag tag = (Tag) level.getData(); if (tag == null) { if (logger.isDebugEnabled()) logger.debug("getLevelOfTreeNode: Exinull (Appears to be an empty level!)"); return null; } // it's a leaf, consisting of a single tag : make a list of books if (logger.isTraceEnabled()) logger.trace("getLevelOfTreeNode: it's a leaf, consisting of a single tag : make a list of books"); String urn = Constants.INITIAL_URN_PREFIX + getCatalogType()+ level.getGuid(); result = getDetailedEntry(pBreadcrumbs, tag, urn, level.getId()); TrookSpecificSearchDatabaseManager.addTag(tag, result); } else { result = getLevelOfTreeNode(pBreadcrumbs, level, 0); } if (logger.isDebugEnabled()) logger.debug("getLevelOfTreeNode: Exit level " + level); return result; } /** * Get the psgrd of entries for a given level in the tree. * * @param pBreadcrumbs * @param level * @param from * @return * @throws IOException */ private Element getLevelOfTreeNode(Breadcrumbs pBreadcrumbs, TreeNode level, int from) throws IOException { if (logger.isDebugEnabled()) logger.debug("getLevelOfTreeNode: pBreadcrumbs=" + pBreadcrumbs + ", level=" + level + ", from=" + from); boolean inSubDir = ((getCatalogLevel().length() > 0) || (from != 0) || pBreadcrumbs.size() > 1); int pageNumber = Summarizer.getPageNumber(from + 1); int itemsCount = level.getChildren().size(); String filename = getCatalogBaseFolderFileName() // // TODO: Get tag id as part of name to help with tracing source + Constants.TYPE_SEPARATOR + encryptString(level.toString()) + Constants.PAGE_DELIM + pageNumber; logger.debug("getLevelOfTreeNode,int: generating " + filename); boolean onRoot = (level.isRoot()); // TODO Might want to make the title include all 'parts' ? String title = (onRoot ? Localization.Main.getText("tags.title") : level.getId()); String urn = Constants.INITIAL_URN_PREFIX + getCatalogType() + Constants.URN_SEPARATOR + encryptString(pBreadcrumbs.toString()); String urlExt = CatalogManager.getCatalogFileUrl(filename + Constants.XML_EXTENSION, inSubDir); String summary = ""; if (onRoot) { int tagsSize = getTags().size(); if (tagsSize > 1) summary = Localization.Main.getText("tags.categorized", tagsSize); else if (tagsSize == 1) summary = Localization.Main.getText("tags.categorized.single"); } else { // try and list the items to make the summary summary = Summarizer.summarizeTagLevels(level.getChildren()); } int maxPages = Summarizer.getPageNumber(itemsCount); List<Element> result = new LinkedList<Element>(); Element feed = FeedHelper.getFeedRootElement(pBreadcrumbs, title, urn, urlExt, true /*inSubDir*/); for (int i = from; i < itemsCount; i++) { if ((i - from) >= maxBeforePaginate) { Element nextLink = getLevelOfTreeNode(pBreadcrumbs, level, i); result.add(0, nextLink); break; } else { TreeNode childLevel = level.getChildren().get(i); Breadcrumbs breadcrumbs = Breadcrumbs.addBreadcrumb(pBreadcrumbs, title, urlExt); Element entry = getLevelOfTreeNode(breadcrumbs, childLevel); if (entry != null) result.add(entry); } } if (logger.isTraceEnabled()) logger.trace("getLevelOfTreeNode: add entry to feed"); feed.addContent(result); Element entry; String urlInItsSubfolder = CatalogManager.getCatalogFileUrl(filename + Constants.XML_EXTENSION, inSubDir); entry = createPaginateLinks(feed, urlExt, pageNumber, maxPages); createFilesFromElement(feed,filename, HtmlManager.FeedType.Catalog, true); if (from == 0) { if (logger.isTraceEnabled()) {logger.trace("getLevelOfTreeNode: Breadcrumbs=" + pBreadcrumbs.toString());} entry = FeedHelper.getCatalogEntry(title, urn, urlInItsSubfolder, summary, useExternalIcons ? getIconPrefix(inSubDir) + Icons.ICONFILE_TAGS : Icons.ICON_TAGS); } if (logger.isDebugEnabled()) logger.debug("getLevelOfTreeNode: Exit level " + level + ", from=" + from); return entry; } }