Ejemplo n.º 1
0
 def runTest(self):
     if not self.real:
         self.assertEqual(self.level.elements, {}) # this is important on undo
     self.f1 = self.level.collect(TestUrl('test://band 1 - song'))
     # due to the fixed url->id mapping, these id is the same on real and editor
     self.assertEqual(self.f1.id, 1)
     self.assertIn(self.f1.id, self.level)
     self.f2 = self.level.collect(TestUrl('test://band 2 - a song'))
     self.f3 = self.level.collect(TestUrl('test://band 3 - another song'))
     self.f4 = self.level.collect(TestUrl('test://band 4 - no song'))
     containerTags = tags.Storage({tags.TITLE: ['Weird album']})
     if self.real: # On real level createContainer does not work until we added the contents to the db
         self.assertEqual(0, db.query("SELECT COUNT(*) FROM {}elements".format(db.prefix)).getSingle())
         from maestro.core import reallevel
         self.assertEqual(reallevel._dbIds,set())
         self.level.addToDb([self.f1, self.f2, self.f3, self.f4])
         self.assertEqual(4, db.query("SELECT COUNT(*) FROM {}elements".format(db.prefix)).getSingle())
         self.assertEqual(reallevel._dbIds,set([1,2,3,4]))
     
     # note that this id will be different when this test is run for editor and real level
     predictedId = db._nextId
     self.assertNotIn(predictedId, self.level) 
     self.assertEqual(self.f1.parents, [])
     self.c = self.level.createContainer(tags=containerTags, contents=[self.f1, self.f2, self.f3])
     self.assertEqual(self.c.id, predictedId)
     self.assertIn(predictedId, self.level)
     self.assertEqual(self.c.contents,
                      elements.ContentList.fromList([self.f1.id, self.f2.id, self.f3.id]))
     self.assertEqual(self.f1.parents, [self.c.id])
     
     self.checkUndo()
     self.checkRedo()
Ejemplo n.º 2
0
 def setUp(self):
     super().setUp()
     self.level.elements = {}
     if self.real:
         db.query("DELETE FROM {}elements".format(db.prefix))
         from maestro.core import reallevel
         reallevel._dbIds = set()
Ejemplo n.º 3
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.º 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 setUp(self):
     super().setUp()
     self.level.elements = {}
     if self.real:
         db.query("DELETE FROM {}elements".format(db.prefix))
         from maestro.core import reallevel
         reallevel._dbIds = set()
Ejemplo n.º 6
0
 def _shouldSkip(self, state):
     """Some states should be skipped, when the database already contains the corresponding values."""
     if state == 'domains':
         return db.query("SELECT COUNT(*) FROM {p}domains").getSingle() > 0
     elif state == 'tags':
         return db.query("SELECT COUNT(*) FROM {p}tagids").getSingle() > 0
     else: return False
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 handleMissingFiles(self):
     """Called after all missing hashes have been computed and all modified files have been examined for
     tag changes.
     """
     self.handleTagAndHashChanges()
     if len(self.missingDB) > 0:  # some files have been (re)moved outside Maestro
         missingHashes = {}  # hashes of missing files mapped to Track objects
         for file in self.missingDB:
             if file.hash is not None:
                 missingHashes[file.hash] = file
         if len(missingHashes) > 0:
             # search newfiles for the missing hashes in order to detect moves
             detectedMoves = []
             for file in self.files.values():
                 if file.id is None and file.hash in missingHashes:
                     oldFile = missingHashes[file.hash]
                     detectedMoves.append((oldFile, file.url))
                     self.missingDB.remove(oldFile)
                     del missingHashes[file.hash]
             for file, newURL in detectedMoves:
                 db.query('UPDATE {p}files SET url=? WHERE element_id=?', str(newURL), file.id)
                 logging.info(__name__, 'renamed outside maestro: {}->{}'.format(file.url, newURL))
                 self.moveFile(file, newURL)
         if len(self.missingDB) > 0:
             # --> some files are lost. Show a dialog and let the user fix this
             from maestro.filesystem import dialogs
             dialog = dialogs.MissingFilesDialog([file.id for file in self.missingDB])
             dialog.exec_()
             stack.clear()
     self.scanState = ScanState.notScanning
     self.scanTimer.stop()
     logging.debug(__name__, 'scan of source {} finished'.format(self.name))
Ejemplo n.º 9
0
 def runTest(self):
     if self.real:
         self.assertEqual(0, db.query("SELECT COUNT(*) FROM {}elements".format(db.prefix)).getSingle())
         self.assertEqual(set(self.level.elements.keys()), set([el.id for el in self.fs]))
     else:
         self.assertEqual(self.level.elements, {})
     print("Start commit")
     self.subLevel.commit()
     if self.real:
         self.assertEqual(5, db.query("SELECT COUNT(*) FROM {}elements".format(db.prefix)).getSingle())
     self.assertEqual(set(self.level.elements.keys()),
                      set([element.id for element in [self.f1, self.f2, self.f3, self.f4, self.c]]))
     self.assertEqual(self.level[self.c.id].contents, self.contentList)
     self.assertIsNot(self.level[self.c.id].contents, self.contentList)
     self.assertEqual(self.level[self.f3.id].parents, [])
     self.assertEqual(self.level[self.c.id].tags, self.containerTags)
     self.assertIsNot(self.level[self.c.id].tags, self.containerTags)
     
     # Now change stuff on the sub level and commit again
     #===================================================
     self.f5 = self.subLevel.collect(TestUrl('test://band 5 - new song'))
     contentList = elements.ContentList.fromPairs([(10,self.f5), (12,self.f2)])
     self.subLevel.changeContents({self.c: contentList})
     self.subLevel.removeElements([self.f1])
     self.assertEqual(self.subLevel.elements.keys(),
                      set([element.id for element in [self.f2, self.f3, self.f4, self.f5, self.c]]))
     self.subLevel.commit()
     self.assertEqual(set(self.level.elements.keys()),
                  set([element.id for element in [self.f1, self.f2, self.f3, self.f4, self.f5, self.c]]))
     self.assertEqual(self.level[self.c.id].contents, contentList)
     
     self.checkUndo()
     self.checkRedo()
Ejemplo n.º 10
0
def query(resource, mbid, includes=[]):
    """Queries MusicBrainz' web service for *resource* with *mbid* and the given list of includes.
    
    Returns an LXML ElementTree root node. All namespaces are removed from the result.
    """
    url = '{}/{}/{}'.format(wsURL, resource, mbid)
    if queryCallback:
        queryCallback(url)
    if len(includes) > 0:
        url += '?inc={}'.format('+'.join(includes))
    logging.debug(__name__, 'querying {}'.format(url))
    ans = db.query("SELECT xml FROM {}musicbrainzqueries WHERE url=?".format(db.prefix), url)
    try:
        data = ans.getSingle()
    except db.EmptyResultException:
        try:
            request = urllib.request.Request(url)
            request.add_header('User-Agent',
                               'Maestro/0.4.0 (https://github.com/maestromusic/maestro)')
            with urllib.request.urlopen(request) as response:
                data = response.read()
        except urllib.error.HTTPError as e:
            if e.code == 404:
                raise e
            else:
                raise ConnectionError(e.msg)
        db.query("INSERT INTO {}musicbrainzqueries (url, xml) VALUES (?,?)"
                 .format(db.prefix), url, data)
    root = etree.fromstring(data)
    # remove namespace tags
    for node in root.iter(): 
        if node.tag.startswith('{'):
            node.tag = node.tag.rsplit('}', 1)[-1]
    return root
Ejemplo n.º 11
0
 def _shouldSkip(self, state):
     """Some states should be skipped, when the database already contains the corresponding values."""
     if state == 'domains':
         return db.query("SELECT COUNT(*) FROM {p}domains").getSingle() > 0
     elif state == 'tags':
         return db.query("SELECT COUNT(*) FROM {p}tagids").getSingle() > 0
     else:
         return False
Ejemplo n.º 12
0
 def getStatistics(self):
     """Gather and return the data for the statistics table."""
     length = db.query("SELECT SUM(length) FROM {}files".format(db.prefix)).getSingle()
     # SQL's SUM returns NULL if files is empty
     if length is None:
         length = 0
         
     return [
         (self.tr("Elements"), db.query("SELECT COUNT(*) FROM {}elements".format(db.prefix)).getSingle()),
         (self.tr("Files"), db.query("SELECT COUNT(*) FROM {}files".format(db.prefix)).getSingle()),
         (self.tr("Total Length"), utils.strings.formatLength(length)),
         (self.tr("Containers"),db.query(
                 "SELECT COUNT(*) FROM {}elements WHERE file = 0"
                 .format(db.prefix)).getSingle()),
         (self.tr("Toplevel elements"),db.query("""
                 SELECT COUNT(*)
                 FROM {0}elements AS el LEFT JOIN {0}contents AS c ON el.id = c.element_id
                 WHERE c.element_id IS NULL
                 """.format(db.prefix)).getSingle()),
         (self.tr("Toplevel files"),db.query("""
                 SELECT COUNT(*)
                 FROM {0}elements AS el LEFT JOIN {0}contents AS c ON el.id = c.element_id
                 WHERE el.file = 1 AND c.element_id IS NULL
                 """.format(db.prefix)).getSingle()),
         (self.tr("Content relations"),db.query(
                 "SELECT COUNT(*) FROM {}contents"
                     .format(db.prefix)).getSingle()),
         (self.tr("Tag relations"),db.query(
                 "SELECT COUNT(*) FROM {}tags"
                     .format(db.prefix)).getSingle()),
         (self.tr("Tracked new files"),db.query(
                 "SELECT COUNT(*) FROM {}newfiles"
                     .format(db.prefix)).getSingle()),
         ]
Ejemplo n.º 13
0
 def undo(self):
     if not exists(dirname(self.tmpPath)):
         os.makedirs(dirname(self.tmpPath))
     shutil.move(self.element.url.path, self.tmpPath)
     tmpUrl = urls.URL.fileURL(self.tmpPath)
     db.query('UPDATE {p}files SET url=? WHERE element_id=?', str(tmpUrl), self.element.id)
     for level in levels.allLevels:
         if self.element.id in level:
             levelElem = level[self.element.id]
             levelElem.url = tmpUrl
             level.emitEvent(dataIds=[self.element.id])
     levels.real.emitFilesystemEvent(deleted=[self.element])
Ejemplo n.º 14
0
    def runTest(self):
        if self.real:
            self.assertEqual(
                0,
                db.query("SELECT COUNT(*) FROM {}elements".format(
                    db.prefix)).getSingle())
            self.assertEqual(set(self.level.elements.keys()),
                             set([el.id for el in self.fs]))
        else:
            self.assertEqual(self.level.elements, {})
        print("Start commit")
        self.subLevel.commit()
        if self.real:
            self.assertEqual(
                5,
                db.query("SELECT COUNT(*) FROM {}elements".format(
                    db.prefix)).getSingle())
        self.assertEqual(
            set(self.level.elements.keys()),
            set([
                element.id
                for element in [self.f1, self.f2, self.f3, self.f4, self.c]
            ]))
        self.assertEqual(self.level[self.c.id].contents, self.contentList)
        self.assertIsNot(self.level[self.c.id].contents, self.contentList)
        self.assertEqual(self.level[self.f3.id].parents, [])
        self.assertEqual(self.level[self.c.id].tags, self.containerTags)
        self.assertIsNot(self.level[self.c.id].tags, self.containerTags)

        # Now change stuff on the sub level and commit again
        #===================================================
        self.f5 = self.subLevel.collect(TestUrl('test://band 5 - new song'))
        contentList = elements.ContentList.fromPairs([(10, self.f5),
                                                      (12, self.f2)])
        self.subLevel.changeContents({self.c: contentList})
        self.subLevel.removeElements([self.f1])
        self.assertEqual(
            self.subLevel.elements.keys(),
            set([
                element.id
                for element in [self.f2, self.f3, self.f4, self.f5, self.c]
            ]))
        self.subLevel.commit()
        self.assertEqual(
            set(self.level.elements.keys()),
            set([
                element.id for element in
                [self.f1, self.f2, self.f3, self.f4, self.f5, self.c]
            ]))
        self.assertEqual(self.level[self.c.id].contents, contentList)

        self.checkUndo()
        self.checkRedo()
Ejemplo n.º 15
0
 def doAction(self):
     elem = next(self.parent().selection.fileWrappers()).element
     path = QtWidgets.QFileDialog.getOpenFileName(
         application.mainWindow,
         self.tr('Select new location of the missing file'),
         os.path.dirname(elem.url.path))[0]
     if path != '':
         newUrl = urls.URL.fileURL(path)
         from maestro.filesystem import getNewfileHash
         db.query('UPDATE {p}files SET url=?,hash=? WHERE element_id=?',
                  str(newUrl), getNewfileHash(newUrl), elem.id)
         elem.url = newUrl
         levels.real.emitEvent(dataIds=(elem.id,))
Ejemplo n.º 16
0
 def undo(self):
     if not exists(dirname(self.tmpPath)):
         os.makedirs(dirname(self.tmpPath))
     shutil.move(self.element.url.path, self.tmpPath)
     tmpUrl = urls.URL.fileURL(self.tmpPath)
     db.query('UPDATE {p}files SET url=? WHERE element_id=?', str(tmpUrl),
              self.element.id)
     for level in levels.allLevels:
         if self.element.id in level:
             levelElem = level[self.element.id]
             levelElem.url = tmpUrl
             level.emitEvent(dataIds=[self.element.id])
     levels.real.emitFilesystemEvent(deleted=[self.element])
Ejemplo n.º 17
0
def enable():
    """Initialize filesystem module. Creates :class:`Source` instances for all configured sources.
    """
    global allSources
    from maestro.filesystem.sources import Source
    allSources = [Source(**data) for data in config.storage.filesystem.sources]
    # delete files not in any source
    if len(allSources) > 0:
        db.query('DELETE FROM {p}newfiles WHERE ' + ' AND '.join('url NOT LIKE "file://{}%"'.format(
                 source.path.replace('"', '\\"')) for source in allSources))
    allSources.sort(key=lambda s: s.name)
    urls.fileBackends.append(RealFile)
    parseAutoReplace()
Ejemplo n.º 18
0
 def getTags(self):
     """Gather and return the data for the tags table."""
     tags = []
     result = db.query("SELECT id, tagname, tagtype, private FROM {}tagids ORDER BY id".format(db.prefix))
     for id,name,type,private in result:
         tuple = (id,name,type,private,
             db.query("SELECT COUNT(*) FROM {}values_{} WHERE tag_id={}"
                      .format(db.prefix,type,id)).getSingle(),
             db.query("SELECT COUNT(*) FROM {}tags WHERE tag_id={}"
                      .format(db.prefix,id)).getSingle()
          )
         tags.append(tuple)
     return(tags)
Ejemplo n.º 19
0
 def doAction(self):
     elem = next(self.parent().selection.fileWrappers()).element
     path = QtWidgets.QFileDialog.getOpenFileName(
         application.mainWindow,
         self.tr('Select new location of the missing file'),
         os.path.dirname(elem.url.path))[0]
     if path != '':
         newUrl = urls.URL.fileURL(path)
         from maestro.filesystem import getNewfileHash
         db.query('UPDATE {p}files SET url=?,hash=? WHERE element_id=?',
                  str(newUrl), getNewfileHash(newUrl), elem.id)
         elem.url = newUrl
         levels.real.emitEvent(dataIds=(elem.id, ))
Ejemplo n.º 20
0
def enable():
    """Initialize filesystem module. Creates :class:`Source` instances for all configured sources.
    """
    global allSources
    from maestro.filesystem.sources import Source
    allSources = [Source(**data) for data in config.storage.filesystem.sources]
    # delete files not in any source
    if len(allSources) > 0:
        db.query('DELETE FROM {p}newfiles WHERE ' + ' AND '.join(
            'url NOT LIKE "file://{}%"'.format(source.path.replace('"', '\\"'))
            for source in allSources))
    allSources.sort(key=lambda s: s.name)
    urls.fileBackends.append(RealFile)
    parseAutoReplace()
Ejemplo n.º 21
0
    def _changeStickers(self, changes):
        if not all(element.isInDb() for element in changes.keys()):
            raise levels.ConsistencyError("Elements on real must be added to the DB before adding stickers.")
        with db.transaction():
            for element, diff in changes.items():
                for type, (a, b) in diff.diffs.items():
                    if a is not None:
                        db.query("DELETE FROM {p}stickers WHERE type=? AND element_id=?",
                                 type, element.id)
                    if b is not None:
                        db.multiQuery(
                            "INSERT INTO {p}stickers (element_id, type, sort, data) VALUES (?,?,?,?)",
                            [(element.id, type, i, val) for i, val in enumerate(b)])

        super()._changeStickers(changes)
Ejemplo n.º 22
0
    def _setStickers(self, type, elementToStickers):
        if not all(element.isInDb() for element in elementToStickers.keys()):
            raise levels.ConsistencyError("Elements on real must be added to the DB before adding stickers.")
        values = []
        for element, stickers in elementToStickers.items():
            if stickers is not None:
                values.extend((element.id, type, i, s) for i, s in enumerate(stickers))
        with db.transaction():
            db.query("DELETE FROM {}stickers WHERE type = ? AND element_id IN ({})"
                     .format(db.prefix, db.csIdList(elementToStickers.keys())), type)
            if len(values) > 0:
                db.multiQuery("INSERT INTO {p}stickers (element_id, type, sort, data) VALUES (?,?,?,?)",
                              values)

        super()._setStickers(type, elementToStickers)
Ejemplo n.º 23
0
 def getTags(self):
     """Gather and return the data for the tags table."""
     tags = []
     result = db.query(
         "SELECT id, tagname, tagtype, private FROM {}tagids ORDER BY id".
         format(db.prefix))
     for id, name, type, private in result:
         tuple = (
             id, name, type, private,
             db.query(
                 "SELECT COUNT(*) FROM {}values_{} WHERE tag_id={}".format(
                     db.prefix, type, id)).getSingle(),
             db.query("SELECT COUNT(*) FROM {}tags WHERE tag_id={}".format(
                 db.prefix, id)).getSingle())
         tags.append(tuple)
     return (tags)
Ejemplo n.º 24
0
 def updateTable(self):
     """Update the result table (in the GUI) with data from the result table (in the database)."""
     self.table.clear()
     rowCount = 0 if self.criterion is None else len(self.criterion.result)
     self.table.setRowCount(rowCount)
     if rowCount == 0:
         return
     # Add the titles. If there are more than one title, concatenate them.
     result = db.query("""
             SELECT el.id AS id, GROUP_CONCAT(v.value {separator} ', ') AS value
             FROM {0}elements AS el
                         LEFT JOIN {0}tags AS t ON el.id = t.element_id AND t.tag_id = {titleTag}
                         LEFT JOIN {0}values_varchar AS v ON t.tag_id = v.tag_id AND t.value_id = v.id
             WHERE el.id IN ({ids})
             GROUP BY el.id
             """.format(db.prefix,
                        separator='SEPARATOR' if db.type == 'mysql' else ',',
                        titleTag=tags.TITLE.id,
                        ids=','.join(str(id) for id in self.criterion.result)))
     for i, row in enumerate(result):
         if i == 0:
             self.table.setColumnCount(len(row))
         for j, data in enumerate(row):
             item = QtWidgets.QTableWidgetItem(str(data))
             item.setFlags(Qt.ItemIsEnabled)
             self.table.setItem(i, j, item)
     self.table.resizeColumnsToContents()
     self.table.setEnabled(True)
Ejemplo n.º 25
0
    def getDates(self):
        """Return heights and labels of the bars in the date chart."""
        result = db.query("""
            SELECT v.value {1} 10000 AS date, COUNT(*) AS count 
            FROM {0}tags AS t JOIN {0}values_date AS v ON t.tag_id = v.tag_id AND t.value_id = v.id
            WHERE t.tag_id = 8
            GROUP BY date
            """.format(db.prefix, '/' if db.type == 'sqlite' else
                       'DIV'))  # SQLite uses integer division
        counters = collections.defaultdict(int)
        for date, count in result:
            if date < 1700:
                date = (date // 100) * 100
            elif date < 1950:
                date = (date // 50) * 50
            else:
                date = (date // 10) * 10
            counters[date] += count

        def dateToStr(date):
            if date < 1700:
                return "{}-{}".format(date, date + 99)
            elif date < 1950:
                return "{}-{}".format(date, date + 49)
            elif date < 2000:  #70s etc.
                return "{}s".format(date % 100)
            else:
                return "{}-{}".format(
                    date, min(date + 9,
                              datetime.date.today().year))

        return [(counters[date], dateToStr(date))
                for date in sorted(counters.keys())]
Ejemplo n.º 26
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.º 27
0
 def updateTable(self):
     """Update the result table (in the GUI) with data from the result table (in the database)."""
     self.table.clear()
     rowCount = 0 if self.criterion is None else len(self.criterion.result)
     self.table.setRowCount(rowCount)
     if rowCount == 0:
         return
     # Add the titles. If there are more than one title, concatenate them.
     result = db.query("""
             SELECT el.id AS id, GROUP_CONCAT(v.value {separator} ', ') AS value
             FROM {0}elements AS el
                         LEFT JOIN {0}tags AS t ON el.id = t.element_id AND t.tag_id = {titleTag}
                         LEFT JOIN {0}values_varchar AS v ON t.tag_id = v.tag_id AND t.value_id = v.id
             WHERE el.id IN ({ids})
             GROUP BY el.id
             """.format(
         db.prefix,
         separator='SEPARATOR' if db.type == 'mysql' else ',',
         titleTag=tags.TITLE.id,
         ids=','.join(str(id) for id in self.criterion.result)))
     for i, row in enumerate(result):
         if i == 0:
             self.table.setColumnCount(len(row))
         for j, data in enumerate(row):
             item = QtWidgets.QTableWidgetItem(str(data))
             item.setFlags(Qt.ItemIsEnabled)
             self.table.setItem(i, j, item)
     self.table.resizeColumnsToContents()
     self.table.setEnabled(True)
Ejemplo n.º 28
0
    def finish(self):
        """Read tag information from table, check it (invalid or duplicate tag names?) and write it to the
        tagids table."""
        # Read tags
        tags = collections.OrderedDict()
        for row in range(self.tableWidget.rowCount()):
            column = self._getColumnIndex('name')
            if self.tableWidget.item(row, column).checkState() != Qt.Checked:
                continue
            name = self.tableWidget.item(row, column).text()

            # Check invalid tag names
            if not isValidTagName(name):
                QtWidgets.QMessageBox.warning(
                    self, self.tr("Invalid tagname"),
                    self.tr("'{}' is not a valid tagname.").format(name))
                return False

            # Check duplicate tag names
            if name in tags:
                QtWidgets.QMessageBox.warning(
                    self, self.tr("Some tags have the same name"),
                    self.tr("There is more than one tag with name '{}'.").
                    format(name))
                return False

            column = self._getColumnIndex('type')
            if row <= 1:
                valueType = self.tableWidget.item(row, column).text()
            else:
                valueType = self.tableWidget.indexWidget(
                    self.tableWidget.model().index(row, column)).currentText()

            column = self._getColumnIndex('title')
            title = self.tableWidget.item(row, column).text()
            if title == '':
                title = None

            column = self._getColumnIndex('icon')
            iconLabel = self.tableWidget.indexWidget(
                self.tableWidget.model().index(row, column))
            icon = iconLabel.path

            column = self._getColumnIndex('private')
            private = self.tableWidget.item(row,
                                            column).checkState() == Qt.Checked
            tags[name] = (name, valueType, title, icon, 1 if private else 0,
                          row + 1)

        # Write tags to database
        assert db.query("SELECT COUNT(*) FROM {}tagids".format(
            db.prefix)).getSingle() == 0
        db.multiQuery(
            "INSERT INTO {}tagids (tagname, tagtype, title, icon, private, sort)"
            " VALUES (?,?,?,?,?,?)".format(db.prefix), tags.values())

        # The first two tags are used as title and album. popitem returns a (key, value) tuple.
        config.options.tags.title_tag = tags.popitem(last=False)[0]
        config.options.tags.album_tag = tags.popitem(last=False)[0]
        return True
Ejemplo n.º 29
0
    def _setContents(self, parent, contents):
        with db.transaction():
            db.query("DELETE FROM {p}contents WHERE container_id = ?", parent.id)
            #Note: This checks skips elements which are not loaded on real. This should rarely happen and
            # due to foreign key constraints...
            if not all(self[childId].isInDb() for childId in contents if childId in self):
                raise levels.ConsistencyError("Elements must be in the DB before being added to a container.")
            
            if len(contents) > 0:
                # ...the following query will fail anyway (but with a DBException)
                # if some contents are not in the database yet.
                db.multiQuery("INSERT INTO {p}contents (container_id, position, element_id) VALUES (?,?,?)",
                              [(parent.id, pos, childId) for pos, childId in contents.items()])
            db.updateElementsCounter((parent.id,))

        super()._setContents(parent, contents)
Ejemplo n.º 30
0
def deleteSuperfluousValues():
    """Remove unused entries from the values_* tables."""
    tables = set(valueType.table for valueType in tagsModule.TYPES)
    for table in tables:
        # This is complicated because we need different queries for MySQL and SQLite.
        # Neither query works in both.
        mainPart = """ FROM {1} LEFT JOIN {0}tags ON {1}.tag_id = {0}tags.tag_id
                                                 AND {1}.id = {0}tags.value_id
                    WHERE element_id IS NULL
                    """.format(db.prefix, table)
        if db.type == 'mysql':
            # Cannot use DELETE together with JOIN in SQLite
            db.query("DELETE {} {}".format(table, mainPart))
        else:
            # Cannot delete from a table used in a subquery in MySQL
            db.query("DELETE FROM {0} WHERE id IN (SELECT {0}.id {1})".format(
                table, mainPart))
Ejemplo n.º 31
0
def getStorage(elid):
    """Return a tags.Storage object filled with the tags of the element with the given id."""
    result = db.query(
        "SELECT tag_id, value_id FROM {p}tags WHERE element_id = ?", elid)
    storage = tagsModule.Storage()
    for tagId, valueId in result:
        tag = tagsModule.get(tagId)
        storage.add(tag, value(tag, valueId))
    return storage
Ejemplo n.º 32
0
def getIdsAndValues(tagSpec, whereClause='1', *args, **kwargs):
    tag = tagsModule.get(tagSpec)
    result = db.query(
        "SELECT id, value FROM {} WHERE tag_id = ? AND {}".format(
            tag.type.table, whereClause), tag.id, *args, **kwargs)
    if tag.type != tagsModule.TYPE_DATE:
        return (tuple(row) for row in result)
    else:
        return ((id, utils.FlexiDate.fromSql(date)) for id, date in result)
Ejemplo n.º 33
0
    def _changeStickers(self, changes):
        if not all(element.isInDb() for element in changes.keys()):
            raise levels.ConsistencyError(
                "Elements on real must be added to the DB before adding stickers."
            )
        with db.transaction():
            for element, diff in changes.items():
                for type, (a, b) in diff.diffs.items():
                    if a is not None:
                        db.query(
                            "DELETE FROM {p}stickers WHERE type=? AND element_id=?",
                            type, element.id)
                    if b is not None:
                        db.multiQuery(
                            "INSERT INTO {p}stickers (element_id, type, sort, data) VALUES (?,?,?,?)",
                            [(element.id, type, i, val)
                             for i, val in enumerate(b)])

        super()._changeStickers(changes)
Ejemplo n.º 34
0
def getValues(tagSpec, whereClause='1', *args, **kwargs):
    tag = tagsModule.get(tagSpec)
    result = db.query(
        "SELECT value FROM {} WHERE tag_id = ? AND {}".format(
            tag.type.table, whereClause), tag.id, *args, **kwargs)
    if tag.type != tagsModule.TYPE_DATE:
        return result.getSingleColumn()
    else:
        return (utils.FlexiDate.fromSql(date)
                for date in result.getSingleColumn())
Ejemplo n.º 35
0
    def runTest(self):
        if not self.real:
            self.assertEqual(self.level.elements,
                             {})  # this is important on undo
        self.f1 = self.level.collect(TestUrl('test://band 1 - song'))
        # due to the fixed url->id mapping, these id is the same on real and editor
        self.assertEqual(self.f1.id, 1)
        self.assertIn(self.f1.id, self.level)
        self.f2 = self.level.collect(TestUrl('test://band 2 - a song'))
        self.f3 = self.level.collect(TestUrl('test://band 3 - another song'))
        self.f4 = self.level.collect(TestUrl('test://band 4 - no song'))
        containerTags = tags.Storage({tags.TITLE: ['Weird album']})
        if self.real:  # On real level createContainer does not work until we added the contents to the db
            self.assertEqual(
                0,
                db.query("SELECT COUNT(*) FROM {}elements".format(
                    db.prefix)).getSingle())
            from maestro.core import reallevel
            self.assertEqual(reallevel._dbIds, set())
            self.level.addToDb([self.f1, self.f2, self.f3, self.f4])
            self.assertEqual(
                4,
                db.query("SELECT COUNT(*) FROM {}elements".format(
                    db.prefix)).getSingle())
            self.assertEqual(reallevel._dbIds, set([1, 2, 3, 4]))

        # note that this id will be different when this test is run for editor and real level
        predictedId = db._nextId
        self.assertNotIn(predictedId, self.level)
        self.assertEqual(self.f1.parents, [])
        self.c = self.level.createContainer(
            tags=containerTags, contents=[self.f1, self.f2, self.f3])
        self.assertEqual(self.c.id, predictedId)
        self.assertIn(predictedId, self.level)
        self.assertEqual(
            self.c.contents,
            elements.ContentList.fromList([self.f1.id, self.f2.id,
                                           self.f3.id]))
        self.assertEqual(self.f1.parents, [self.c.id])

        self.checkUndo()
        self.checkRedo()
Ejemplo n.º 36
0
    def moveFile(self, file: File, newUrl: urls.URL):
        """Internally move *file* to *newUrl* by updating the folders and their states.

        This does not alter the filesystem and normally also not the database. The exception is
        the target URL already exist in self.files; in that case it is removed from newfiles.
        """
        newDir = self.getFolder(newUrl.directory)
        oldDir = file.folder
        oldDir.files.remove(file)
        if newUrl.path in self.files:
            newDir.files.remove(self.files[newUrl.path])
            db.query('DELETE FROM {p}newfiles WHERE url=?', str(newUrl))
        newDir.add(file)
        del self.files[file.url.path]
        file.url = newUrl
        self.files[newUrl.path] = file
        newDir.updateState(True, emit=self.folderStateChanged)
        if oldDir != newDir:
            oldDir.updateState(True, emit=self.folderStateChanged)
        self.fileStateChanged.emit(newUrl.path)
Ejemplo n.º 37
0
    def _setStickers(self, type, elementToStickers):
        if not all(element.isInDb() for element in elementToStickers.keys()):
            raise levels.ConsistencyError(
                "Elements on real must be added to the DB before adding stickers."
            )
        values = []
        for element, stickers in elementToStickers.items():
            if stickers is not None:
                values.extend(
                    (element.id, type, i, s) for i, s in enumerate(stickers))
        with db.transaction():
            db.query(
                "DELETE FROM {}stickers WHERE type = ? AND element_id IN ({})".
                format(db.prefix, db.csIdList(elementToStickers.keys())), type)
            if len(values) > 0:
                db.multiQuery(
                    "INSERT INTO {p}stickers (element_id, type, sort, data) VALUES (?,?,?,?)",
                    values)

        super()._setStickers(type, elementToStickers)
Ejemplo n.º 38
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.º 39
0
 def getContainerTypes(self):
     """Return sizes and labels for each wedge in the container types chart. The percentage of a wedge
     is its size divided by the sum of all sizes times 100."""
     result = db.query("""
         SELECT COUNT(*) AS count, type
         FROM {p}elements
         WHERE file = 0
         GROUP BY type
         ORDER BY count DESC
         """)
     return [(row[0], elements.ContainerType(row[1]).title())
             for row in result]
Ejemplo n.º 40
0
 def finish(self):
     """Read tag information from table, check it (invalid or duplicate tag names?) and write it to the
     tagids table."""
     # Read tags
     tags = collections.OrderedDict()
     for row in range(self.tableWidget.rowCount()):
         column = self._getColumnIndex('name')
         if self.tableWidget.item(row, column).checkState() != Qt.Checked:
             continue
         name = self.tableWidget.item(row, column).text()
         
         # Check invalid tag names
         if not isValidTagName(name):
             QtWidgets.QMessageBox.warning(self, self.tr("Invalid tagname"),
                                       self.tr("'{}' is not a valid tagname.").format(name))
             return False
         
         # Check duplicate tag names
         if name in tags:
             QtWidgets.QMessageBox.warning(self, self.tr("Some tags have the same name"),
                                       self.tr("There is more than one tag with name '{}'.").format(name))
             return False
             
         column = self._getColumnIndex('type')
         if row <= 1:
             valueType = self.tableWidget.item(row, column).text()
         else: valueType = self.tableWidget.indexWidget(
                                             self.tableWidget.model().index(row, column)).currentText()
         
         column = self._getColumnIndex('title')
         title = self.tableWidget.item(row, column).text()
         if title == '':
             title = None
             
         column = self._getColumnIndex('icon')
         iconLabel = self.tableWidget.indexWidget(self.tableWidget.model().index(row, column))
         icon = iconLabel.path
         
         column = self._getColumnIndex('private')
         private = self.tableWidget.item(row, column).checkState() == Qt.Checked
         tags[name] = (name, valueType, title, icon, 1 if private else 0, row+1)
     
     # Write tags to database
     assert db.query("SELECT COUNT(*) FROM {}tagids".format(db.prefix)).getSingle() == 0
     db.multiQuery("INSERT INTO {}tagids (tagname, tagtype, title, icon, private, sort)"
                   " VALUES (?,?,?,?,?,?)"
                   .format(db.prefix), tags.values())
     
     # The first two tags are used as title and album. popitem returns a (key, value) tuple.
     config.options.tags.title_tag = tags.popitem(last=False)[0]
     config.options.tags.album_tag = tags.popitem(last=False)[0] 
     return True
Ejemplo n.º 41
0
 def replaceTrack(self, wavName, tracknumber):
     os.remove(join(self.tmpdir, wavName))
     flacPath = join(self.tmpdir, wavName[:-3] + 'flac')
     try:
         ans = db.query("SELECT element_id FROM {}files WHERE url LIKE 'audiocd://{}.{}/%'"
                        .format(db.prefix, self.discid, tracknumber)).getSingle()
         elem = levels.real.collect(ans)
         levels.real.stack.push(InsertRippedFileCommand(elem, flacPath))
     except db.EmptyResultException:
         finishedTracks.append((self.discid, tracknumber, flacPath))
     if tracknumber == self.toTrack:
         mainwindow.mainWindow.statusBar().removeWidget(self.statusWidget)
         self.encodingProcess = self.ripProcess = None
Ejemplo n.º 42
0
    def _setContents(self, parent, contents):
        with db.transaction():
            db.query("DELETE FROM {p}contents WHERE container_id = ?",
                     parent.id)
            #Note: This checks skips elements which are not loaded on real. This should rarely happen and
            # due to foreign key constraints...
            if not all(self[childId].isInDb()
                       for childId in contents if childId in self):
                raise levels.ConsistencyError(
                    "Elements must be in the DB before being added to a container."
                )

            if len(contents) > 0:
                # ...the following query will fail anyway (but with a DBException)
                # if some contents are not in the database yet.
                db.multiQuery(
                    "INSERT INTO {p}contents (container_id, position, element_id) VALUES (?,?,?)",
                    [(parent.id, pos, childId)
                     for pos, childId in contents.items()])
            db.updateElementsCounter((parent.id, ))

        super()._setContents(parent, contents)
Ejemplo n.º 43
0
 def redo(self):
     target = self.element.url.path
     if not exists(dirname(target)):
         os.makedirs(dirname(target))
     shutil.move(self.tmpPath, target)
     newUrl = urls.URL.fileURL(target)
     tmpFile = filesystem.RealFile(newUrl)
     tmpFile.readTags()
     length = tmpFile.length
     tmpFile.tags = self.element.tags.withoutPrivateTags(copy=True)
     tracknr = parseNetloc(self.element.url)[1]
     tmpFile.specialTags = dict(tracknumber=str(tracknr))
     tmpFile.saveTags()
     db.query('UPDATE {p}files SET url=?, length=? WHERE element_id=?',
              str(newUrl), length, self.element.id)
     for level in levels.allLevels:
         if self.element.id in level:
             levelElem = level[self.element.id]
             levelElem.length = length
             levelElem.url = newUrl
             level.emitEvent(dataIds=[self.element.id])
     levels.real.emitFilesystemEvent(added=[self.element])
Ejemplo n.º 44
0
 def redo(self):
     target = self.element.url.path
     if not exists(dirname(target)):
         os.makedirs(dirname(target))
     shutil.move(self.tmpPath, target)
     newUrl = urls.URL.fileURL(target)
     tmpFile = filesystem.RealFile(newUrl)
     tmpFile.readTags()
     length = tmpFile.length
     tmpFile.tags = self.element.tags.withoutPrivateTags(copy=True)
     tracknr = parseNetloc(self.element.url)[1]
     tmpFile.specialTags = dict(tracknumber=str(tracknr))
     tmpFile.saveTags()
     db.query('UPDATE {p}files SET url=?, length=? WHERE element_id=?',
              str(newUrl), length, self.element.id)
     for level in levels.allLevels:
         if self.element.id in level:
             levelElem = level[self.element.id]
             levelElem.length = length
             levelElem.url = newUrl
             level.emitEvent(dataIds=[self.element.id])
     levels.real.emitFilesystemEvent(added=[self.element])
Ejemplo n.º 45
0
    def load(self):
        """Load files and newfiles tables, creating the internal structure of File and Folder objects."""
        for elid, urlstring, elhash, verified in db.query(
                    'SELECT element_id, url, hash, verified FROM {p}files WHERE url LIKE ' +
                        "'{}%'".format('file://' + self.path.replace("'", "\\'"))):
            url = urls.URL(urlstring)
            if url.extension in self.extensions:
                self.addFile(url, id=elid, verified=verified, hash=elhash, store=False)

        toDelete = []
        for urlstring, elhash, verified in db.query("SELECT url, hash, verified FROM {p}newfiles "
            + "WHERE url LIKE '{}%'".format('file://' + self.path.replace("'", "\\'"))):
            url = urls.URL(urlstring)
            if url.extension in self.extensions:
                if url.path in self.files:
                    toDelete.append((urlstring,))
                    continue
                self.addFile(url, hash=elhash, verified=verified, store=False)
            else:
                toDelete.append((urlstring,))
        if len(toDelete):
            db.multiQuery('DELETE FROM {p}newfiles WHERE url=?', toDelete)
Ejemplo n.º 46
0
def query(resource, mbid, includes=[]):
    """Queries MusicBrainz' web service for *resource* with *mbid* and the given list of includes.
    
    Returns an LXML ElementTree root node. All namespaces are removed from the result.
    """
    url = '{}/{}/{}'.format(wsURL, resource, mbid)
    if queryCallback:
        queryCallback(url)
    if len(includes) > 0:
        url += '?inc={}'.format('+'.join(includes))
    logging.debug(__name__, 'querying {}'.format(url))
    ans = db.query(
        "SELECT xml FROM {}musicbrainzqueries WHERE url=?".format(db.prefix),
        url)
    try:
        data = ans.getSingle()
    except db.EmptyResultException:
        try:
            request = urllib.request.Request(url)
            request.add_header(
                'User-Agent',
                'Maestro/0.4.0 (https://github.com/maestromusic/maestro)')
            with urllib.request.urlopen(request) as response:
                data = response.read()
        except urllib.error.HTTPError as e:
            if e.code == 404:
                raise e
            else:
                raise ConnectionError(e.msg)
        db.query(
            "INSERT INTO {}musicbrainzqueries (url, xml) VALUES (?,?)".format(
                db.prefix), url, data)
    root = etree.fromstring(data)
    # remove namespace tags
    for node in root.iter():
        if node.tag.startswith('{'):
            node.tag = node.tag.rsplit('}', 1)[-1]
    return root
Ejemplo n.º 47
0
 def __init__(self, parent):
     super().__init__(parent=parent)
     self.addField('icon',
                   self.tr("Icon"),
                   'image',
                   folders=[':maestro/flags', ':maestro/tags'])
     self.addField('name', self.tr("Name"), 'string')
     self.addField('number', self.tr("# of elements"), 'fixed')
     self.items = list(flags.allFlags())
     # Cache the element counts (we assume that they never change while this model is used)
     self.elementCounts = dict(
         db.query(
             "SELECT flag_id, COUNT(*) FROM {p}flags GROUP BY flag_id"))
     application.dispatcher.connect(self._handleDispatcher)
Ejemplo n.º 48
0
 def replaceTrack(self, wavName, tracknumber):
     os.remove(join(self.tmpdir, wavName))
     flacPath = join(self.tmpdir, wavName[:-3] + 'flac')
     try:
         ans = db.query(
             "SELECT element_id FROM {}files WHERE url LIKE 'audiocd://{}.{}/%'"
             .format(db.prefix, self.discid, tracknumber)).getSingle()
         elem = levels.real.collect(ans)
         levels.real.stack.push(InsertRippedFileCommand(elem, flacPath))
     except db.EmptyResultException:
         finishedTracks.append((self.discid, tracknumber, flacPath))
     if tracknumber == self.toTrack:
         mainwindow.mainWindow.statusBar().removeWidget(self.statusWidget)
         self.encodingProcess = self.ripProcess = None
Ejemplo n.º 49
0
def sortValue(tagSpec, valueId, valueIfNone=False):
    """Returns the sort value for the given tag value, or None if it is not set.
    
    If *valueIfNone=True*, the value itself is returned if no sort value is set."""
    tag = tagsModule.get(tagSpec)
    value, sortValue = db.query(
        "SELECT value, sort_value FROM {} WHERE tag_id = ? AND id = ?".format(
            tag.type.table), tag.id, valueId).getSingleRow()
    if sortValue is not None:
        return sortValue
    elif valueIfNone:
        return value
    else:
        return None
Ejemplo n.º 50
0
 def check(self,name,iconPath,redo=True):
     result = db.query("SELECT name,icon FROM {}flag_names WHERE name='testflag' OR name='testflag2'"
             .format(db.prefix))
     if name is None: # no flag should exist
         self.assertRaises(db.sql.EmptyResultException,result.getSingle)
     else:
         for dbName,dbIconPath in result:
             if db.isNull(dbIconPath):
                 dbIconPath = None
             flag = flags.get(name)
             self.assertEqual(flag.name,name)
             self.assertEqual(dbName,name)
             self.assertEqual(flag.iconPath,iconPath)
             self.assertEqual(dbIconPath,iconPath)
             break;
         else: self.fail() # two flags found by the db query
Ejemplo n.º 51
0
 def check(self,values,redo=True):
     result = db.query("SELECT tagtype,title,icon,private,sort FROM {}tagids WHERE tagname='testtag'"
                       .format(db.prefix))
     if values is not None:
         dbValues = [v if not db.isNull(v) else None for v in result.getSingleRow()]
         dbValues[3] = bool(dbValues[3]) # private
         dbValues = tuple(dbValues)
                         
         self.assertEqual(dbValues,values)
         tag = tags.get("testtag")
         self.assertEqual(tag.type.name,values[0])
         self.assertEqual(tag.rawTitle,values[1])
         self.assertEqual(tag.iconPath,values[2])
         self.assertEqual(tag.private,values[3])
         self.assertEqual(tags.tagList.index(tag),values[4])
     else:
         self.assertRaises(db.sql.EmptyResultException,result.getSingleRow)
Ejemplo n.º 52
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.º 53
0
    def runTest(self):
        # Create the table
        if self.type == 'mysql':
            db.query("""
                CREATE TEMPORARY TABLE {}{} (
                id INT UNSIGNED NOT NULL AUTO_INCREMENT,
                name VARCHAR(30) NOT NULL,
                age INT NOT NULL,
                size DOUBLE NOT NULL,
                male BOOLEAN NOT NULL,
                death INT NULL DEFAULT NULL,
                PRIMARY KEY(id)
                ) ENGINE InnoDB, CHARACTER SET 'utf8';
                """.format(db.prefix,testTable)
            )
        else:
             db.query("""
                CREATE TABLE {}{} (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                name VARCHAR(30) NOT NULL,
                age INT NOT NULL,
                size DOUBLE NOT NULL,
                male BOOLEAN NOT NULL,
                death INT NULL DEFAULT NULL
                )
                """.format(db.prefix,testTable)
            )
        
        # Fill it with data
        result = db.query("INSERT INTO {}{} (name,age,size,male,death) VALUES (?,?,?,?,?)"
                                .format(db.prefix,testTable),*data[0]) # without death column
        self.assertEqual(result.insertId(),1)

        result = db.multiQuery("INSERT INTO {}{} (name,age,size,male,death) VALUES (?,?,?,?,?)"
                                .format(db.prefix,testTable),data[1:])
        # Neither affectedRows nor insertId are equal for different drivers after a multiQuery
        self.assertEqual(db.query("SELECT COUNT(*) FROM {}{}".format(db.prefix,testTable)).getSingle(),4)

        # And retrieve it again
        result = db.query("SELECT id,name,age,size,male,death FROM {}{} ORDER BY id".format(db.prefix,testTable))
        self.assertEqual(len(result),4)
        for i,row in enumerate(result):
            self.assertEqual(i+1,row[0]) # id
            for j in range(5):
                self.assertEqual(data[i][j],row[j+1] if j+1<5 else utils.FlexiDate.fromSql(row[j+1]))

        # Check getSingle* methods
        result = db.query("SELECT id FROM {}{} WHERE age = ?".format(db.prefix,testTable),24)
        self.assertEqual(result.getSingle(),1)

        result = db.query("SELECT id FROM {}{} ORDER BY id".format(db.prefix,testTable))
        for i,v in enumerate(result.getSingleColumn()):
            self.assertEqual(i+1,v)

        result = db.query("SELECT id,age FROM {}{} WHERE id = ?".format(db.prefix,testTable),2)
        row = result.getSingleRow()
        self.assertEqual(row[0],2)
        self.assertEqual(row[1],data[1][1])

        # Start modifying the data
        result = db.query("DELETE FROM {}{} WHERE death IS NOT NULL".format(db.prefix,testTable))
        self.assertEqual(result.affectedRows(),1)

        # Test transactions
        db.transaction()
        for i in range(1,4):
            db.query("UPDATE {}{} SET age=age+1 WHERE id = ?".format(db.prefix,testTable),i)
        db.commit()

        result = db.query("SELECT age FROM {}{} ORDER BY id".format(db.prefix,testTable))
        self.assertListEqual(list(result.getSingleColumn()),[25,23,22])

        db.transaction()
        for i in range(1,4):
            db.query("UPDATE {}{} SET death = ?".format(db.prefix,testTable),utils.FlexiDate(2000))
        db.rollback()

        result = db.query("SELECT death FROM {}{}".format(db.prefix,testTable))
        self.assertListEqual(list(utils.FlexiDate.fromSql(value) for value in result.getSingleColumn()),
                             3*[None])

        # Check exceptions
        self.assertRaises(db.sql.DBException,lambda: db.query("STUPID QUERY"))
        
        result = db.query("SELECT * FROM {}{} WHERE death IS NOT NULL".format(db.prefix,testTable))
        self.assertRaises(db.sql.EmptyResultException,result.getSingle)
        self.assertRaises(db.sql.EmptyResultException,result.getSingleRow)
Ejemplo n.º 54
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.º 55
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.º 56
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.º 57
0
    def _addToDb(self, elements):
        """Like addToDb but not undoable."""
        if len(elements) == 0:
            return # multiquery will fail otherwise
        
        for element in elements:
            assert not element.isInDb()
            assert element.level is self
            if element.id not in self:
                assert element.isContainer()
                self.elements[element.id] = element
            else: assert element.isFile() 
        
        with db.transaction():
            data = [(element.domain.id,
                     element.id,
                     element.isFile(),
                     element.type.value if element.isContainer() else 0,
                     len(element.contents) if element.isContainer() else 0)
                            for element in elements]
            db.multiQuery("INSERT INTO {p}elements (domain, id, file, type, elements) VALUES (?,?,?,?,?)",
                          data)
    
            # Do this early, otherwise e.g. setFlags might raise a ConsistencyError)
            _dbIds.update(element.id for element in elements)
                
            for element in elements:
                # Set tags
                db.query("DELETE FROM {p}tags WHERE element_id = ?", element.id)
                for tag in element.tags:
                    db.multiQuery("INSERT INTO {p}tags (element_id,tag_id,value_id) VALUES (?,?,?)",
                          [(element.id, tag.id, db.tags.id(tag, value, insert=True))
                           for value in element.tags[tag]])
                
                # Set flags
                db.query("DELETE FROM {p}flags WHERE element_id = ?", element.id)
                if len(element.flags) > 0:
                    db.multiQuery("INSERT INTO {p}flags (element_id, flag_id) VALUES (?,?)",
                                  [(element.id, flag.id) for flag in element.flags])
    
                # Set stickers
                db.query("DELETE FROM {p}stickers WHERE element_id = ?", element.id)
                for stickerType, values in element.stickers.items():
                    db.multiQuery("INSERT INTO {p}stickers (element_id, type, sort, data) VALUES (?,?,?,?)",
                                  [(element.id, stickerType, i, val) for i, val in enumerate(values)])
                    
            newFiles = [element for element in elements if element.isFile()]
            if len(newFiles) > 0:
                from .. import filesystem
                db.multiQuery('INSERT INTO {p}files (element_id, url, hash, verified, length)'
                              'VALUES (?, ?, ?, ?, ?)',
                              [(element.id, str(element.url), filesystem.getNewfileHash(element.url),
                               time.time(), element.length) for element in newFiles])
                self.emitFilesystemEvent(added=[f for f in newFiles if f.url.scheme == 'file'])
            
            contentData = []
            for element in elements:
                if element.isContainer():
                    contentData.extend((element.id, item[0], item[1]) for item in element.contents.items())
                    for childId in element.contents:
                        if element.id not in self[childId].parents:
                            self[childId].parents.append(element.id)
                        
            if len(contentData) > 0:
                db.multiQuery("INSERT INTO {p}contents (container_id, position, element_id) VALUES (?,?,?)",
                              contentData)

        self.emit(levels.LevelChangeEvent(dbAddedIds=[el.id for el in elements]))