Ejemplo n.º 1
0
    def doAction(self):
        treeView = self.parent()

        # Collect tags
        tags = set()
        from maestro.widgets.browser.model import TagLayer
        for layer in treeView.model().layers:
            if isinstance(layer, TagLayer):
                tags.update(layer.tagList)

        # Collect values
        valuesToHide = {tag: [] for tag in tags}
        for wrapper in treeView.selection.wrappers():
            if not wrapper.isContainer():
                continue

            # Collect all children ids and tag values
            wrapper.loadContents(recursive=True)
            ids = []
            allValues = {tag: set() for tag in tags}
            for child in wrapper.getAllNodes(skipSelf=True):
                element = child.element
                ids.append(element.id)
                for tag in tags:
                    if tag in element.tags:
                        allValues[tag].update(
                            db.tags.id(tag, value)
                            for value in element.tags[tag])

            # Check which tag values appear outside the children ids (thus possibly in wrapper itself)
            for tag, vids in allValues.items():
                if len(vids) > 0:
                    result = db.query("""
                        SELECT value_id
                        FROM {p}tags
                        WHERE tag_id=? AND element_id NOT IN ({elids}) AND value_id IN ({vids})
                        GROUP BY value_id
                        """,
                                      tag.id,
                                      elids=db.csList(ids),
                                      vids=db.csList(vids))
                    # We will not hide the values returned from the query because they appear elsewhere
                    vids.difference_update(result.getSingleColumn())
                    # these lists should be disjoint for different wrappers
                    # (unless one is an ancestor of the other one)
                    valuesToHide[tag].extend(vids)

            dialog = HideTagValuesDialog(treeView, valuesToHide)
            dialog.exec_()
Ejemplo n.º 2
0
    def accept(self):
        from maestro.plugins.coverdesk.plugin import StackItem, CoverItem
        stack = StackItem(self.titleEdit.text())
        if self.allButton.isChecked():
            toplevel = list(
                db.query(
                    """
                    SELECT id
                    FROM {p}elements
                    WHERE domain=? AND id NOT IN (SELECT element_id FROM {p}contents)
                    ORDER BY id
                    """, self.scene.domain.id).getSingleColumn())
            elements = levels.real.collect(toplevel)
            stack.items = [CoverItem(self.scene, el) for el in elements]
        elif self.searchButton.isChecked():
            criterion = self.searchBox.criterion
            if criterion is None:
                return  # do not accept
            search.search(criterion, domain=self.scene.domain)
            elids = criterion.result
            toplevel = set(elids)
            toplevel.difference_update(
                db.query(
                    "SELECT element_id FROM {p}contents WHERE container_id IN ({elids})",
                    elids=db.csList(elids)).getSingleColumn())
            toplevel = sorted(elids)
            elements = levels.real.collect(toplevel)
            stack.items = [CoverItem(self.scene, el) for el in elements]

        stack.setPos(self.scene.makePosition(self.position))
        self.scene.addItem(stack)
        super().accept()
Ejemplo n.º 3
0
    def _loaded(self, task):
        """Load covers after search for elements has finished. If no search was necessary, *task* is None.
        """
        if task is not None:
            if not isinstance(task, search.SearchTask): # subclasses might submit over tasks
                return
            elids = task.criterion.result
            if len(elids):
                filterClause = " AND el.id IN ({})".format(db.csList(elids))
            else:
                self.display().setCovers([], {})
                return
        else:
            filterClause = " AND el.domain={}".format(self.domain.id)
    
        result = db.query("""
            SELECT el.id, st.data
            FROM {p}elements AS el
                JOIN {p}stickers AS st ON el.id = st.element_id
            WHERE st.type = 'COVER' {filter}
            """, filter=filterClause)
        coverPaths = {id: path for id, path in result}
        ids = list(coverPaths.keys())
        levels.real.collect(ids)
        if tags.isInDb("artist") and tags.isInDb("date"):
            sortValues = {}
            artistTag = tags.get("artist")
            dateTag = tags.get("date")
            for id in ids:
                el = levels.real[id]
                sortValues[id] = (el.tags[artistTag][0] if artistTag in el.tags else utils.PointAtInfinity(),
                                  el.tags[dateTag][0] if dateTag in el.tags else utils.PointAtInfinity())
            ids.sort(key=sortValues.get)

        self.display().setCovers(ids, coverPaths)
Ejemplo n.º 4
0
 def accept(self):
     from maestro.plugins.coverdesk.plugin import StackItem, CoverItem
     stack = StackItem(self.titleEdit.text())
     if self.allButton.isChecked():
         toplevel = list(db.query("""
                 SELECT id
                 FROM {p}elements
                 WHERE domain=? AND id NOT IN (SELECT element_id FROM {p}contents)
                 ORDER BY id
                 """, self.scene.domain.id).getSingleColumn())
         elements = levels.real.collect(toplevel)
         stack.items = [CoverItem(self.scene, el) for el in elements]
     elif self.searchButton.isChecked():
         criterion = self.searchBox.criterion
         if criterion is None:
             return # do not accept
         search.search(criterion, domain=self.scene.domain)
         elids = criterion.result
         toplevel = set(elids)
         toplevel.difference_update(db.query(
                  "SELECT element_id FROM {p}contents WHERE container_id IN ({elids})",
                  elids=db.csList(elids)).getSingleColumn())
         toplevel = sorted(elids)
         elements = levels.real.collect(toplevel)
         stack.items = [CoverItem(self.scene, el) for el in elements]
         
     stack.setPos(self.scene.makePosition(self.position))
     self.scene.addItem(stack)
     super().accept()
     
Ejemplo n.º 5
0
    def _removeFromDb(self, elements):
        """Like removeFromDb but not undoable."""
        for element in elements:
            assert element.isInDb()
            if element.isContainer():
                del self.elements[element.id]
                for childId in element.contents:
                    self[childId].parents.remove(element.id)
        _dbIds.difference_update(element.id for element in elements)

        # Rely on foreign keys to delete all tags, flags etc. from the database
        ids = itertools.chain.from_iterable(element.contents
                                            for element in elements
                                            if element.isContainer())
        db.query("DELETE FROM {}elements WHERE id IN ({})".format(
            db.prefix, db.csList(element.id for element in elements)))
        removedFiles = [
            element.url for element in elements
            if element.isFile() and element.url.scheme == "file"
        ]
        if len(removedFiles) > 0:
            self.emitFilesystemEvent(removed=removedFiles)
        self.checkGlobalSelection(elements)
        self.emit(
            levels.LevelChangeEvent(dbRemovedIds=[el.id for el in elements]))
Ejemplo n.º 6
0
 def doAction(self):
     treeView = self.parent()
     
     # Collect tags
     tags = set()
     from maestro.widgets.browser.model import TagLayer
     for layer in treeView.model().layers:
         if isinstance(layer, TagLayer):
             tags.update(layer.tagList)
     
     # Collect values
     valuesToHide = {tag: [] for tag in tags}
     for wrapper in treeView.selection.wrappers():
         if not wrapper.isContainer():
             continue
         
         # Collect all children ids and tag values 
         wrapper.loadContents(recursive=True)
         ids = []
         allValues = {tag: set() for tag in tags}
         for child in wrapper.getAllNodes(skipSelf=True):
             element = child.element
             ids.append(element.id)
             for tag in tags:
                 if tag in element.tags:
                     allValues[tag].update(db.tags.id(tag, value) for value in element.tags[tag])
                 
         # Check which tag values appear outside the children ids (thus possibly in wrapper itself)
         for tag, vids in allValues.items():
             if len(vids) > 0:
                 result = db.query("""
                     SELECT value_id
                     FROM {p}tags
                     WHERE tag_id=? AND element_id NOT IN ({elids}) AND value_id IN ({vids})
                     GROUP BY value_id
                     """, tag.id, elids=db.csList(ids), vids=db.csList(vids))
                 # We will not hide the values returned from the query because they appear elsewhere
                 vids.difference_update(result.getSingleColumn())
                 # these lists should be disjoint for different wrappers
                 # (unless one is an ancestor of the other one)
                 valuesToHide[tag].extend(vids)
         
         dialog = HideTagValuesDialog(treeView, valuesToHide)
         dialog.exec_()
Ejemplo n.º 7
0
 def _removeFromDb(self, elements):
     """Like removeFromDb but not undoable."""
     for element in elements:
         assert element.isInDb()
         if element.isContainer():
             del self.elements[element.id]
             for childId in element.contents:
                 self[childId].parents.remove(element.id)
     _dbIds.difference_update(element.id for element in elements)
     
     # Rely on foreign keys to delete all tags, flags etc. from the database
     ids = itertools.chain.from_iterable(element.contents for element in elements
                                                          if element.isContainer())
     db.query("DELETE FROM {}elements WHERE id IN ({})"
              .format(db.prefix, db.csList(element.id for element in elements)))
     removedFiles = [element.url for element in elements if element.isFile()
                                                         and element.url.scheme == "file"]
     if len(removedFiles) > 0:
         self.emitFilesystemEvent(removed=removedFiles)
     self.checkGlobalSelection(elements)
     self.emit(levels.LevelChangeEvent(dbRemovedIds=[el.id for el in elements]))
Ejemplo n.º 8
0
 def loadFromDb(self, idList, level=None):
     """Load elements specified by *idList* from the database into *level* which defaults to the
     real level."""
     if level is None:
         level = self
         
     if len(idList) == 0: # queries will fail otherwise
         return []
     
     csIdList = db.csList(idList)
     
     # bare elements
     result = db.query("""
             SELECT el.domain, el.id, el.file, el.type, f.url, f.length
             FROM {0}elements AS el LEFT JOIN {0}files AS f ON el.id = f.element_id
             WHERE el.id IN ({1})
             """.format(db.prefix, csIdList))
     for domainId, id, file, elementType, url, length in result:
         _dbIds.add(id)
         if file:
             level.elements[id] = elements.File(domains.domainById(domainId), level, id,
                                                url=URL(url), length=length,
                                                type=elements.ContainerType(elementType))
         else:
             level.elements[id] = elements.Container(domains.domainById(domainId), level, id,
                                                     type=elements.ContainerType(elementType))
             
     # contents
     result = db.query("""
             SELECT el.id, c.position, c.element_id
             FROM {0}elements AS el JOIN {0}contents AS c ON el.id = c.container_id
             WHERE el.id IN ({1})
             ORDER BY position
             """.format(db.prefix, csIdList))
     for id, pos, contentId in result:
         level.elements[id].contents.insert(pos, contentId)
         
     # parents
     result = db.query("""
             SELECT el.id, c.container_id
             FROM {0}elements AS el JOIN {0}contents AS c ON el.id = c.element_id
             WHERE el.id IN ({1})
             """.format(db.prefix, csIdList))
     for id, contentId in result:
         level.elements[id].parents.append(contentId)
         
     # tags
     result = db.query("""
             SELECT el.id, t.tag_id, t.value_id
             FROM {0}elements AS el JOIN {0}tags AS t ON el.id = t.element_id
             WHERE el.id IN ({1})
             """.format(db.prefix, csIdList))
     for id, tagId, valueId in result:
         tag = tags.get(tagId)
         level.elements[id].tags.add(tag, db.tags.value(tag, valueId))
         
     # flags
     result = db.query("""
             SELECT el.id, f.flag_id
             FROM {0}elements AS el JOIN {0}flags AS f ON el.id = f.element_id
             WHERE el.id IN ({1})
             """.format(db.prefix, csIdList))
     for id, flagId in result:
         level.elements[id].flags.append(flags.get(flagId))
         
     # stickers
     result = db.query("""
             SELECT element_id, type, data
             FROM {}stickers
             WHERE element_id IN ({})
             ORDER BY element_id, type, sort
             """.format(db.prefix, csIdList))
     # This is a bit complicated because the stickers should be stored in tuples, not lists
     # Changing the lists would break undo/redo
     #TODO: is this really necessary?
     current = None
     buffer = []
     for (id, type, sticker) in result:
         if current is None:
             current = (id, type)
         elif current != (id, type):
             level.elements[current[0]].stickers[current[1]] = tuple(buffer)
             current = (id, type)
             buffer = []
         element = level.elements[id]
         if element.stickers is None:
             element.stickers = {}
         buffer.append(sticker)
     if current is not None:
         level.elements[current[0]].stickers[current[1]] = tuple(buffer)
     
     try:
         return [self.elements[id] for id in idList]
     except KeyError: # probably some ids were not contained in the database
         raise levels.ElementGetError(self, [id for id in idList if id not in self])
Ejemplo n.º 9
0
def _buildContainerTree(domain, elids):
    """Create a wrapper tree including all elements from *elids* (or all elements with the given domain,
    if *elids* is None). The tree will organize wrappers according to the natural tree structure.
    """
    if elids is None:
        toplevel = list(
            db.query(
                """
                SELECT id
                FROM {p}elements
                WHERE domain=? AND id NOT IN (SELECT element_id FROM {p}contents)
                """, domain.id).getSingleColumn())
    elif len(elids) > 0:
        toplevel = set(elids)
        toplevel.difference_update(
            db.query(
                "SELECT element_id FROM {}contents WHERE container_id IN ({})".
                format(db.prefix, db.csList(elids))).getSingleColumn())
    else:
        return []

    # Load all toplevel elements and all of their ancestors
    newIds = toplevel
    while len(newIds) > 0:
        levels.real.collect(newIds)
        nextIds = []
        for id in newIds:
            nextIds.extend(levels.real[id].parents)
        newIds = nextIds

    # Collect all parents in cDict (mapping parent id -> list of children ids)
    # Parents contained as key in this dict, will only contain part of their element's contants in
    # the browser. The part is given by the corresponding value (a list) in this dict.
    # The dict must not contain direct search results as keys, as they should always show all their
    # contents.
    cDict = collections.defaultdict(list)

    def processNode(id):
        """Check whether the element with the given id has major parents that need to be added to the 
        browser's tree. If such a parent is found, update cDict and toplevel and return True.
        """
        result = False
        for pid in levels.real[id].parents:
            if pid in cDict:  # We've already added this parent
                result = True
                cDict[pid].append(id)
                toplevel.discard(id)
            elif elids is None or pid in elids:  # This parent belongs to the direct search result
                result = True
                toplevel.discard(id)
            elif processNode(
                    pid
            ):  # We must add this parent, because it has a major ancestor.
                result = True
                cDict[pid].append(id)
                toplevel.discard(id)
            elif levels.real[
                    pid].type.major:  # This is a major parent. Add it to toplevel
                result = True
                cDict[pid].append(id)
                toplevel.discard(id)
                toplevel.add(pid)
        return result

    for id in list(toplevel):  # copy!
        processNode(id)

    def createWrapper(id):
        """Create a wrapper to be inserted in the browser. If the wrapper should contain all of its
        element's contents, create a BrowserWrapper, that will load the contents """
        element = levels.real[id]
        if id in cDict:  # wrapper should contain only a part of its element's contents
            wrapper = Wrapper(element)
            wrapper.setContents([createWrapper(cid) for cid in cDict[id]])
            return wrapper
        elif element.isFile() or len(element.contents) == 0:
            return Wrapper(element)
        else:
            return bnodes.BrowserWrapper(
                element
            )  # a wrapper that will load all its contents when needed

    contents = [createWrapper(id) for id in toplevel]

    def sortFunction(wrapper):
        """Intelligent sort: sort albums by their date, everything else by name."""
        element = wrapper.element
        date = 0
        if element.isContainer(
        ) and element.type == elements.ContainerType.Album:
            dateTag = tags.get("date")
            if dateTag.type == tags.TYPE_DATE and dateTag in element.tags:
                date = -element.tags[dateTag][0].toSql(
                )  # minus leads to descending sort
        return (date, element.getTitle(neverShowIds=True))

    contents.sort(key=sortFunction)
    return contents
Ejemplo n.º 10
0
    def build(self, layerIndex, domain, elids, matchingTags):
        # 1. Get toplevel nodes.
        if elids is None:
            toplevel = set(
                db.query(
                    """
                    SELECT id
                    FROM {p}elements
                    WHERE domain=? AND id NOT IN (SELECT element_id FROM {p}contents)
                    """, domain.id).getSingleColumn())
            if len(toplevel) == 0:
                return []
        elif len(elids) > 0:
            toplevel = set(elids)
            toplevel.difference_update(
                db.query(
                    "SELECT element_id FROM {p}contents WHERE container_id IN ({elids})",
                    elids=db.csList(elids)).getSingleColumn())
        else:
            return []

        # Shortcut: For very small result sets simply use a container tree
        if len(toplevel) <= 5:
            return _buildContainerTree(domain, elids)

        # 2. Check whether a VariousNode is necessary.
        # (that is, some toplevel nodes don't have a tag from self.tagList)
        # We do this so early because 'toplevel' will be enlarged in the next step.
        tagFilter = db.csIdList(self.tagList)
        idFilter = db.csList(toplevel)
        variousNodeElements = list(
            db.query("""
            SELECT el.id
            FROM {p}elements AS el LEFT JOIN {p}tags AS t
                                ON el.id = t.element_id AND t.tag_id IN ({tagFilter})
            WHERE domain={domain} AND el.id IN ({idFilter}) AND t.value_id IS NULL
            LIMIT 1
            """,
                     tagFilter=tagFilter,
                     idFilter=idFilter,
                     domain=domain.id).getSingleColumn())

        # 3. Add contents of permeable nodes to 'toplevel', as long as they are in the search result.
        # Tag values in these nodes should get a TagNode even if
        # they don't appear in an actual toplevel node.
        new = toplevel
        while len(new):
            new = set(
                db.query("""
                SELECT c.element_id
                FROM {p}contents AS c JOIN {p}elements AS el ON c.container_id = el.id
                WHERE el.type IN ({collection},{container}) AND el.id IN ({parents})""",
                         collection=elements.ContainerType.Collection.value,
                         container=elements.ContainerType.Container.value,
                         parents=db.csList(new)).getSingleColumn())
            # Restrict to search result. If the node's value only appears in contents of a permeable
            # node in the search result and these contents are not in the result themselves,
            # we would create an empty TagNode.
            if elids is not None:
                new.intersection_update(elids)
            toplevel.update(new)

        # 4. Create a TagNode for each tag value that appears in 'toplevel'
        # Make sure to use as single TagNode for equal values in different tags
        nodes = collections.defaultdict(
            functools.partial(bnodes.TagNode, layerIndex))
        idFilter = db.csList(toplevel)
        result = db.query("""
            SELECT DISTINCT t.tag_id, v.id, v.value, v.hide, v.sort_value
            FROM {p}tags AS t JOIN {p}values_varchar AS v ON t.tag_id = v.tag_id AND t.value_id = v.id
            WHERE t.tag_id IN ({tagFilter}) AND t.element_id IN ({idFilter})
            """,
                          tagFilter=tagFilter,
                          idFilter=idFilter)
        for tagId, valueId, value, hide, sortValue in result:
            matching = (tagId, valueId) in matchingTags
            nodes[value].addTagValue(tagId, valueId, value, hide, sortValue,
                                     matching)

        # 5. Optimize TagNodes (if there are only few of them)
        if len(nodes) <= 20:
            # Above we had to use values as keys, here ids are more useful
            nodes = {
                tagTuple: node
                for node in nodes.values() for tagTuple in node.tagIds
            }
            # The first task is to find all contents of each TagNode. Note that the last query (to find
            # TagNodes) only considered toplevel elements.
            for node in nodes.values():
                node.elids = set()
            if elids is not None:
                idFilter = "t.element_id IN ({})".format(db.csList(elids))
            else:
                idFilter = '1'
            result = db.query("""
                SELECT DISTINCT tag_id, value_id, element_id
                FROM {p}tags AS t
                WHERE t.tag_id IN ({tagFilter}) AND {idFilter}
                """,
                              tagFilter=tagFilter,
                              idFilter=idFilter)
            withinMatchingTags = set()
            for tagId, valueId, elementId in result:
                if (tagId, valueId) in nodes:
                    node = nodes[(tagId, valueId)]
                else:
                    continue  # this means that this value does not appear in a 'toplevel' node
                node.elids.add(elementId)
                if (tagId, valueId) in matchingTags:
                    withinMatchingTags.add(elementId)

            def checkSuperNode(node, superNode):
                """Given two nodes where the second contains all contents of the first, return whether
                the first node may be deleted. As a sideeffect this method may merge *node* into *superNode*.
                """
                # If *node* is completely contained in superNode, delete it.
                if len(node.elids) < len(superNode.elids):
                    # However, matching nodes must not be deleted in favor of not matching ones.
                    # and visible nodes must not be deleted in favor of a hidden superNode.
                    return not ((node.matching and not superNode.matching) or
                                (not node.hide and superNode.hide))
                else:
                    if node.hide == superNode.hide:
                        superNode.merge(node)
                        return True
                    else:
                        return node.hide

            for k in list(nodes.keys()):
                node = nodes[k]
                # Delete nodes whose contents are covered by nodes matching the search query.
                if not node.matching and node.elids <= withinMatchingTags:
                    del nodes[k]
                    continue
                # Try to delete (or merge) TagNodes whose contents are contained in (or equal to) another
                # TagNode
                for node2 in nodes.values():
                    if node2 is not node and node.elids <= node2.elids:  # node2 is a superNode:
                        if checkSuperNode(node, node2):
                            del nodes[k]
                            break

        # 6. Create final list of nodes
        visibleNodes = [node for node in nodes.values() if not node.hide]
        hiddenNodes = [node for node in nodes.values() if node.hide]
        visibleNodes.sort(
            key=lambda node: locale.strxfrm(node.sortValues[0][0]))
        hiddenNodes.sort(
            key=lambda node: locale.strxfrm(node.sortValues[0][0]))

        if len(variousNodeElements) > 0:
            node = bnodes.VariousNode(layerIndex, self.tagList)
            visibleNodes.append(node)

        if len(hiddenNodes) > 0:
            # If hidden nodes are present this layer needs two actual levels in the tree structure
            # Since this interferes with the algorithm to determine the layer of a node, we have to store
            # that layer index. See BrowserModel._getLayerIndex
            for node in hiddenNodes:
                node.layer = self
            visibleNodes.append(bnodes.HiddenValuesNode(hiddenNodes))

        return visibleNodes
Ejemplo n.º 11
0
def _buildContainerTree(domain, elids):
    """Create a wrapper tree including all elements from *elids* (or all elements with the given domain,
    if *elids* is None). The tree will organize wrappers according to the natural tree structure.
    """
    if elids is None:
        toplevel = list(db.query("""
                SELECT id
                FROM {p}elements
                WHERE domain=? AND id NOT IN (SELECT element_id FROM {p}contents)
                """, domain.id).getSingleColumn())
    elif len(elids) > 0:
        toplevel = set(elids)
        toplevel.difference_update(db.query(
                 "SELECT element_id FROM {}contents WHERE container_id IN ({})"
                .format(db.prefix, db.csList(elids))).getSingleColumn())
    else:
        return []
        
    # Load all toplevel elements and all of their ancestors
    newIds = toplevel
    while len(newIds) > 0:
        levels.real.collect(newIds)
        nextIds = []
        for id in newIds:
            nextIds.extend(levels.real[id].parents)
        newIds = nextIds

    # Collect all parents in cDict (mapping parent id -> list of children ids)
    # Parents contained as key in this dict, will only contain part of their element's contants in
    # the browser. The part is given by the corresponding value (a list) in this dict.
    # The dict must not contain direct search results as keys, as they should always show all their
    # contents.
    cDict = collections.defaultdict(list)
        
    def processNode(id):
        """Check whether the element with the given id has major parents that need to be added to the 
        browser's tree. If such a parent is found, update cDict and toplevel and return True.
        """
        result = False
        for pid in levels.real[id].parents:
            if pid in cDict: # We've already added this parent
                result = True
                cDict[pid].append(id)
                toplevel.discard(id)
            elif elids is None or pid in elids: # This parent belongs to the direct search result
                result = True
                toplevel.discard(id)
            elif processNode(pid): # We must add this parent, because it has a major ancestor.
                result = True
                cDict[pid].append(id)
                toplevel.discard(id)
            elif levels.real[pid].type.major: # This is a major parent. Add it to toplevel
                result = True
                cDict[pid].append(id)
                toplevel.discard(id)
                toplevel.add(pid)
        return result
    
    for id in list(toplevel): # copy!
        processNode(id)
    
    def createWrapper(id):
        """Create a wrapper to be inserted in the browser. If the wrapper should contain all of its
        element's contents, create a BrowserWrapper, that will load the contents """
        element = levels.real[id]
        if id in cDict: # wrapper should contain only a part of its element's contents
            wrapper = Wrapper(element)
            wrapper.setContents([createWrapper(cid) for cid in cDict[id]])
            return wrapper
        elif element.isFile() or len(element.contents) == 0:
            return Wrapper(element)
        else:
            return bnodes.BrowserWrapper(element) # a wrapper that will load all its contents when needed
    
    contents = [createWrapper(id) for id in toplevel]
    
    def sortFunction(wrapper):
        """Intelligent sort: sort albums by their date, everything else by name."""
        element = wrapper.element
        date = 0
        if element.isContainer() and element.type == elements.ContainerType.Album:
            dateTag = tags.get("date")
            if dateTag.type == tags.TYPE_DATE and dateTag in element.tags: 
                date = -element.tags[dateTag][0].toSql() # minus leads to descending sort
        return (date, element.getTitle(neverShowIds=True))
    
    contents.sort(key=sortFunction)
    return contents
Ejemplo n.º 12
0
    def build(self, layerIndex, domain, elids, matchingTags):
        # 1. Get toplevel nodes.
        if elids is None:
            toplevel = set(db.query("""
                    SELECT id
                    FROM {p}elements
                    WHERE domain=? AND id NOT IN (SELECT element_id FROM {p}contents)
                    """, domain.id).getSingleColumn())
            if len(toplevel) == 0:
                return []
        elif len(elids) > 0:
            toplevel = set(elids)
            toplevel.difference_update(db.query(
                     "SELECT element_id FROM {p}contents WHERE container_id IN ({elids})",
                     elids=db.csList(elids)).getSingleColumn())
        else:
            return []
        
        # Shortcut: For very small result sets simply use a container tree
        if len(toplevel) <= 5:
            return _buildContainerTree(domain, elids)
        
        # 2. Check whether a VariousNode is necessary.
        # (that is, some toplevel nodes don't have a tag from self.tagList)
        # We do this so early because 'toplevel' will be enlarged in the next step.
        tagFilter = db.csIdList(self.tagList)
        idFilter = db.csList(toplevel)
        variousNodeElements = list(db.query("""
            SELECT el.id
            FROM {p}elements AS el LEFT JOIN {p}tags AS t
                                ON el.id = t.element_id AND t.tag_id IN ({tagFilter})
            WHERE domain={domain} AND el.id IN ({idFilter}) AND t.value_id IS NULL
            LIMIT 1
            """, tagFilter=tagFilter, idFilter=idFilter, domain=domain.id).getSingleColumn())
    
        # 3. Add contents of permeable nodes to 'toplevel', as long as they are in the search result.
        # Tag values in these nodes should get a TagNode even if
        # they don't appear in an actual toplevel node.
        new = toplevel
        while len(new):
            new = set(db.query("""
                SELECT c.element_id
                FROM {p}contents AS c JOIN {p}elements AS el ON c.container_id = el.id
                WHERE el.type IN ({collection},{container}) AND el.id IN ({parents})""",
                    collection=elements.ContainerType.Collection.value,
                    container=elements.ContainerType.Container.value,
                    parents=db.csList(new)).getSingleColumn())
            # Restrict to search result. If the node's value only appears in contents of a permeable
            # node in the search result and these contents are not in the result themselves,
            # we would create an empty TagNode.
            if elids is not None:
                new.intersection_update(elids)
            toplevel.update(new)

        # 4. Create a TagNode for each tag value that appears in 'toplevel'
        # Make sure to use as single TagNode for equal values in different tags 
        nodes = collections.defaultdict(functools.partial(bnodes.TagNode, layerIndex))
        idFilter = db.csList(toplevel)
        result = db.query("""
            SELECT DISTINCT t.tag_id, v.id, v.value, v.hide, v.sort_value
            FROM {p}tags AS t JOIN {p}values_varchar AS v ON t.tag_id = v.tag_id AND t.value_id = v.id
            WHERE t.tag_id IN ({tagFilter}) AND t.element_id IN ({idFilter})
            """, tagFilter=tagFilter, idFilter=idFilter)
        for tagId, valueId, value, hide, sortValue in result:
            matching = (tagId, valueId) in matchingTags
            nodes[value].addTagValue(tagId, valueId, value, hide, sortValue, matching)
            
        # 5. Optimize TagNodes (if there are only few of them)
        if len(nodes) <= 20:
            # Above we had to use values as keys, here ids are more useful
            nodes = {tagTuple: node for node in nodes.values() for tagTuple in node.tagIds}
            # The first task is to find all contents of each TagNode. Note that the last query (to find
            # TagNodes) only considered toplevel elements. 
            for node in nodes.values():
                node.elids = set()
            if elids is not None:
                idFilter = "t.element_id IN ({})".format(db.csList(elids))
            else:
                idFilter = '1'
            result = db.query("""
                SELECT DISTINCT tag_id, value_id, element_id
                FROM {p}tags AS t
                WHERE t.tag_id IN ({tagFilter}) AND {idFilter}
                """, tagFilter=tagFilter, idFilter=idFilter)
            withinMatchingTags = set()
            for tagId, valueId, elementId in result:
                if (tagId, valueId) in nodes:
                    node = nodes[(tagId, valueId)]
                else:
                    continue # this means that this value does not appear in a 'toplevel' node
                node.elids.add(elementId)
                if (tagId, valueId) in matchingTags:
                    withinMatchingTags.add(elementId)
                    
            def checkSuperNode(node, superNode):
                """Given two nodes where the second contains all contents of the first, return whether
                the first node may be deleted. As a sideeffect this method may merge *node* into *superNode*.
                """
                # If *node* is completely contained in superNode, delete it.
                if len(node.elids) < len(superNode.elids):
                    # However, matching nodes must not be deleted in favor of not matching ones.
                    # and visible nodes must not be deleted in favor of a hidden superNode.
                    return not ((node.matching and not superNode.matching)
                                or (not node.hide and superNode.hide)) 
                else:
                    if node.hide == superNode.hide:
                        superNode.merge(node)
                        return True
                    else:
                        return node.hide
                
            for k in list(nodes.keys()):
                node = nodes[k]
                # Delete nodes whose contents are covered by nodes matching the search query.
                if not node.matching and node.elids <= withinMatchingTags:
                    del nodes[k]
                    continue
                # Try to delete (or merge) TagNodes whose contents are contained in (or equal to) another
                # TagNode
                for node2 in nodes.values():
                    if node2 is not node and node.elids <= node2.elids: # node2 is a superNode:
                        if checkSuperNode(node, node2):
                            del nodes[k]
                            break
        
        # 6. Create final list of nodes
        visibleNodes = [node for node in nodes.values() if not node.hide]
        hiddenNodes = [node for node in nodes.values() if node.hide]       
        visibleNodes.sort(key=lambda node: locale.strxfrm(node.sortValues[0][0]))
        hiddenNodes.sort(key=lambda node: locale.strxfrm(node.sortValues[0][0]))
        
        if len(variousNodeElements) > 0:
            node = bnodes.VariousNode(layerIndex, self.tagList)
            visibleNodes.append(node)
            
        if len(hiddenNodes) > 0:
            # If hidden nodes are present this layer needs two actual levels in the tree structure
            # Since this interferes with the algorithm to determine the layer of a node, we have to store
            # that layer index. See BrowserModel._getLayerIndex
            for node in hiddenNodes:
                node.layer = self
            visibleNodes.append(bnodes.HiddenValuesNode(hiddenNodes))
        
        return visibleNodes
Ejemplo n.º 13
0
    def loadFromDb(self, idList, level=None):
        """Load elements specified by *idList* from the database into *level* which defaults to the
        real level."""
        if level is None:
            level = self

        if len(idList) == 0:  # queries will fail otherwise
            return []

        csIdList = db.csList(idList)

        # bare elements
        result = db.query("""
                SELECT el.domain, el.id, el.file, el.type, f.url, f.length
                FROM {0}elements AS el LEFT JOIN {0}files AS f ON el.id = f.element_id
                WHERE el.id IN ({1})
                """.format(db.prefix, csIdList))
        for domainId, id, file, elementType, url, length in result:
            _dbIds.add(id)
            if file:
                level.elements[id] = elements.File(
                    domains.domainById(domainId),
                    level,
                    id,
                    url=URL(url),
                    length=length,
                    type=elements.ContainerType(elementType))
            else:
                level.elements[id] = elements.Container(
                    domains.domainById(domainId),
                    level,
                    id,
                    type=elements.ContainerType(elementType))

        # contents
        result = db.query("""
                SELECT el.id, c.position, c.element_id
                FROM {0}elements AS el JOIN {0}contents AS c ON el.id = c.container_id
                WHERE el.id IN ({1})
                ORDER BY position
                """.format(db.prefix, csIdList))
        for id, pos, contentId in result:
            level.elements[id].contents.insert(pos, contentId)

        # parents
        result = db.query("""
                SELECT el.id, c.container_id
                FROM {0}elements AS el JOIN {0}contents AS c ON el.id = c.element_id
                WHERE el.id IN ({1})
                """.format(db.prefix, csIdList))
        for id, contentId in result:
            level.elements[id].parents.append(contentId)

        # tags
        result = db.query("""
                SELECT el.id, t.tag_id, t.value_id
                FROM {0}elements AS el JOIN {0}tags AS t ON el.id = t.element_id
                WHERE el.id IN ({1})
                """.format(db.prefix, csIdList))
        for id, tagId, valueId in result:
            tag = tags.get(tagId)
            level.elements[id].tags.add(tag, db.tags.value(tag, valueId))

        # flags
        result = db.query("""
                SELECT el.id, f.flag_id
                FROM {0}elements AS el JOIN {0}flags AS f ON el.id = f.element_id
                WHERE el.id IN ({1})
                """.format(db.prefix, csIdList))
        for id, flagId in result:
            level.elements[id].flags.append(flags.get(flagId))

        # stickers
        result = db.query("""
                SELECT element_id, type, data
                FROM {}stickers
                WHERE element_id IN ({})
                ORDER BY element_id, type, sort
                """.format(db.prefix, csIdList))
        # This is a bit complicated because the stickers should be stored in tuples, not lists
        # Changing the lists would break undo/redo
        #TODO: is this really necessary?
        current = None
        buffer = []
        for (id, type, sticker) in result:
            if current is None:
                current = (id, type)
            elif current != (id, type):
                level.elements[current[0]].stickers[current[1]] = tuple(buffer)
                current = (id, type)
                buffer = []
            element = level.elements[id]
            if element.stickers is None:
                element.stickers = {}
            buffer.append(sticker)
        if current is not None:
            level.elements[current[0]].stickers[current[1]] = tuple(buffer)

        try:
            return [self.elements[id] for id in idList]
        except KeyError:  # probably some ids were not contained in the database
            raise levels.ElementGetError(
                self, [id for id in idList if id not in self])