Ejemplo n.º 1
0
    def setItemData(self, flag, field, value):
        if field.name == 'name':
            oldName = flag.name
            newName = value
            if oldName == newName:
                return False

            if not flags.isValidFlagname(newName):
                dialogs.warning(
                    self.tr("Cannot change flag"),
                    self.tr("'{}' is not a valid flagname.").format(newName))
                return False

            if flags.exists(newName):
                dialogs.warning(
                    self.tr("Cannot change flag"),
                    self.tr("A flag named '{}' does already exist.").format(
                        newName))
                return False

            flags.changeFlagType(flag, name=newName)
            return True
        elif field.name == 'icon':
            flags.changeFlagType(flag, iconPath=value)
            return True
        else:
            assert False
Ejemplo n.º 2
0
 def openDialog(parent, model, layer=None):
     """Open a dialog to configure a new or existing TagLayer."""
     tagList = layer.tagList if layer is not None else TagLayer.defaultTagList(
     )
     text, ok = QtWidgets.QInputDialog.getText(
         parent,
         translate("TagLayer", "Configure tag layer"),
         translate(
             "TagLayer", "Enter the names/titles of the tags that should "
             "be used to group elements."),
         text=', '.join(tag.title for tag in tagList))
     if ok:
         try:
             tagList = [
                 tags.fromTitle(name.strip()) for name in text.split(',')
             ]
             if len(tagList) == 0 \
                     or not all(tag.isInDb() and tag.type == tags.TYPE_VARCHAR for tag in tagList):
                 raise ValueError()
         except:
             dialogs.warning(
                 translate("TagLayer", "Invalid value"),
                 translate(
                     "TagLayer",
                     "Only varchar-tags registered in the database may be used."
                 ), parent)
         else:
             return TagLayer(tagList)
     return None
Ejemplo n.º 3
0
 def handlePlayerError(self, error):
     if error == self.qtPlayer.FormatError:
         from maestro.gui.dialogs import warning
         warning(self.tr('Playback Failed'), self.tr('Playback failed: format error'))
         self.stop()
     else:
         print('unknown player error {}'.format(error))
Ejemplo n.º 4
0
 def handlePlayerError(self, error):
     if error == self.qtPlayer.FormatError:
         from maestro.gui.dialogs import warning
         warning(self.tr('Playback Failed'),
                 self.tr('Playback failed: format error'))
         self.stop()
     else:
         print('unknown player error {}'.format(error))
Ejemplo n.º 5
0
 def useDBTags(self):
     backendFile = self.file.url.backendFile()
     backendFile.readTags()
     backendFile.tags = self.dbTags.withoutPrivateTags()
     try:
         backendFile.saveTags()
         self.accept()
     except OSError as e:
         from maestro.gui.dialogs import warning
         warning(self.tr('Unable to save tags'), 'Could not save tags:\n{}'.format(e))
         self.reject()
Ejemplo n.º 6
0
 def doAction(self):
     model = self.parent().model()
     if not model.containsExternalTags():
         try:
             model.commit()
         except urls.TagWriteError as e:
             e.displayMessage()
         except levels.RenameFilesError as e:
             e.displayMessage()
     else:
         dialogs.warning(self.tr('No commit possible'),
                         self.tr("Can't commit while editor contains external tags."))
Ejemplo n.º 7
0
 def useDBTags(self):
     backendFile = self.file.url.backendFile()
     backendFile.readTags()
     backendFile.tags = self.dbTags.withoutPrivateTags()
     try:
         backendFile.saveTags()
         self.accept()
     except OSError as e:
         from maestro.gui.dialogs import warning
         warning(self.tr('Unable to save tags'),
                 'Could not save tags:\n{}'.format(e))
         self.reject()
Ejemplo n.º 8
0
    def accept(self):
        """Check the data in the input fields and create a new profile."""
        name = self.nameLineEdit.text()
        type = self.category.types[self.typeBox.currentIndex()]

        if len(name) == 0:
            return

        if self.category.get(name) is not None:
            dialogs.warning(self.tr("Invalid name"), self.tr("There is already a profile of this name."))
        else:
            self.profile = self.category.addProfile(name, type)
        super().accept()
Ejemplo n.º 9
0
 def _editCommonStart(self):
     """Handle 'edit common start' action from context menu."""
     selectedRecords = [editor.getRecord() for editor in self.selectionManager.getSelectedWidgets()]
     commonStart = utils.strings.commonPrefix(str(record.value) for record in selectedRecords)
     text, ok = QtWidgets.QInputDialog.getText(self, self.tr("Edit common start"),
                      self.tr("Insert a new text which will replace the common start "
                              "of all selected records:"),
                      text=commonStart)
     if ok:
         newValues = [text+record.value[len(commonStart):] for record in selectedRecords]
         if all(record.tag.isValid(value) for record, value in zip(selectedRecords, newValues)):
             self._editMany(selectedRecords, newValues)
         else: dialogs.warning(self.tr("Invalid value"), self.tr("One or more values are invalid."))
Ejemplo n.º 10
0
    def doAction(self):
        # device, theDiscid, trackCount = '/dev/sr0', 'qx_MV1nqkljh.L37bA_rgVoyAgU-', 3
        ans = self.askForDiscId()
        if ans is None:
            return
        device, theDiscid, trackCount = ans
        from . import ripper

        self.ripper = ripper.Ripper(device, theDiscid)
        if config.options.audiocd.earlyrip:
            self.ripper.start()
        try:
            release = self._getRelease(theDiscid)
            if release is None:
                return
            progress = dialogs.WaitingDialog("Querying MusicBrainz", "please wait", False)
            progress.open()

            def callback(url):
                progress.setText(self.tr("Fetching data from:\n{}").format(url))
                QtWidgets.qApp.processEvents()

            xmlapi.queryCallback = callback
            xmlapi.fillReleaseForDisc(release, theDiscid)
            progress.close()
            xmlapi.queryCallback = None
            QtWidgets.qApp.processEvents()
            stack = self.level().stack.createSubstack(modalDialog=True)
            level = levels.Level("audiocd", self.level(), stack=stack)
            dialog = ImportAudioCDDialog(level, release)
            if dialog.exec_():
                model = self.parent().model()
                model.insertElements(model.root, len(model.root.contents), [dialog.container])
                if not config.options.audiocd.earlyrip:
                    self.ripper.start()
            stack.close()
        except xmlapi.UnknownDiscException:
            dialog = SimpleRipDialog(theDiscid, trackCount, self.level())
            if dialog.exec_():
                if not config.options.audiocd.earlyrip:
                    self.ripper.start()
                self.level().stack.beginMacro(self.tr("Load Audio CD"))
                model = self.parent().model()
                model.insertElements(model.root, len(model.root.contents), [dialog.container])
                self.level().stack.endMacro()
        except ConnectionError as e:
            dialogs.warning(self.tr('Error communicating with MusicBrainz'), str(e))
            if 'progress' in locals():
                progress.close()
Ejemplo n.º 11
0
    def accept(self):
        """Check the data in the input fields and create a new profile."""
        name = self.nameLineEdit.text()
        type = self.category.types[self.typeBox.currentIndex()]

        if len(name) == 0:
            return

        if self.category.get(name) is not None:
            dialogs.warning(
                self.tr("Invalid name"),
                self.tr("There is already a profile of this name."))
        else:
            self.profile = self.category.addProfile(name, type)
        super().accept()
Ejemplo n.º 12
0
 def _handleRenameButton(self):
     """Ask the user for a new name of the current profile and change names."""
     if self.profile is None:
         return
     text, ok = QtWidgets.QInputDialog.getText(self,
                                           self.tr("Profile name"),
                                           self.tr("Choose a new name:"),
                                           text=self.profile.name)
     if ok and len(text) > 0:
         existingProfile = self.category.get(text)
         if existingProfile == self.profile:
             return  # no change
         elif existingProfile is not None:
             dialogs.warning(self.tr("Invalid name"), self.tr("There is already a profile of this name."))
         else:
             self.category.renameProfile(self.profile, text)
Ejemplo n.º 13
0
 def _handleRenameButton(self):
     """Ask the user for a new name of the current profile and change names."""
     if self.profile is None:
         return
     text, ok = QtWidgets.QInputDialog.getText(
         self,
         self.tr("Profile name"),
         self.tr("Choose a new name:"),
         text=self.profile.name)
     if ok and len(text) > 0:
         existingProfile = self.category.get(text)
         if existingProfile == self.profile:
             return  # no change
         elif existingProfile is not None:
             dialogs.warning(
                 self.tr("Invalid name"),
                 self.tr("There is already a profile of this name."))
         else:
             self.category.renameProfile(self.profile, text)
Ejemplo n.º 14
0
 def next(self):
     if self.stackedLayout.currentIndex() == 0:
         if not self.configWidget.criterionLineEdit.isValid():
             dialogs.warning(translate("wtf", "Invalid criterion"),
                             translate("wtf", "The given filter criterion is invalid"))
             return
         model = buildFileTree(self.configWidget.profile)
         if not model:
             return
         self.fileTree.setModel(model)
         self.statLabel.setText(self.tr("Exporting {} files with a total length of {}.")
                                        .format(model.fileCount,
                                                utils.strings.formatLength(model.totalLength)))
         self.stackedLayout.setCurrentIndex(1)
         self.previousButton.setEnabled(True)
         self.nextButton.setText(self.tr("Finish"))
         return
     else:
         #TODO
         self.accept()
Ejemplo n.º 15
0
    def askForDiscId():
        """Asks the user for a CD-ROM device to use.
        :returns: Three-tuple of the *device*, *disc id*, and number of tracks.
        """
        import discid

        device, ok = QtWidgets.QInputDialog.getText(
            mainwindow.mainWindow,
            translate('AudioCD Plugin', 'Select device'),
            translate('AudioCD Plugin', 'CDROM device:'),
            QtWidgets.QLineEdit.Normal,
            discid.get_default_device())
        if not ok:
            return None
        try:
            with discid.read(device) as disc:
                disc.read()
        except discid.disc.DiscError as e:
                dialogs.warning(translate("AudioCD Plugin", "CDROM drive is empty"), str(e))
                return None
        return device, disc.id, len(disc.tracks)
Ejemplo n.º 16
0
 def openDialog(parent, model, layer=None):
     """Open a dialog to configure a new or existing TagLayer."""
     tagList = layer.tagList if layer is not None else TagLayer.defaultTagList()
     text, ok = QtWidgets.QInputDialog.getText(parent, translate("TagLayer", "Configure tag layer"),
                     translate("TagLayer", "Enter the names/titles of the tags that should "
                               "be used to group elements."),
                     text=', '.join(tag.title for tag in tagList))
     if ok:
         try:
             tagList = [tags.fromTitle(name.strip()) for name in text.split(',')]
             if len(tagList) == 0 \
                     or not all(tag.isInDb() and tag.type == tags.TYPE_VARCHAR for tag in tagList):
                 raise ValueError()
         except:
             dialogs.warning(
                     translate("TagLayer", "Invalid value"),
                     translate("TagLayer", "Only varchar-tags registered in the database may be used."),
                     parent)
         else:
             return TagLayer(tagList)
     return None
Ejemplo n.º 17
0
 def next(self):
     if self.stackedLayout.currentIndex() == 0:
         if not self.configWidget.criterionLineEdit.isValid():
             dialogs.warning(
                 translate("wtf", "Invalid criterion"),
                 translate("wtf", "The given filter criterion is invalid"))
             return
         model = buildFileTree(self.configWidget.profile)
         if not model:
             return
         self.fileTree.setModel(model)
         self.statLabel.setText(
             self.tr(
                 "Exporting {} files with a total length of {}.").format(
                     model.fileCount,
                     utils.strings.formatLength(model.totalLength)))
         self.stackedLayout.setCurrentIndex(1)
         self.previousButton.setEnabled(True)
         self.nextButton.setText(self.tr("Finish"))
         return
     else:
         #TODO
         self.accept()
Ejemplo n.º 18
0
def createNewFlagType(parent=None):
    """Ask the user to supply a name and then create a new flag with this name. Return the new flag or None
    if no flag was created (e.g. if the user aborted the dialog or the supplied name was invalid).
    """
    name = dialogs.getText(
        translate("FlagManager", "New Flag"),
        translate("FlagManager", "Please enter the name of the new flag:"),
        parent)
    if name is None:
        return None

    if flags.exists(name):
        dialogs.warning(
            translate("FlagManager", "Cannot create flag"),
            translate("FlagManager", "This flag does already exist."), parent)
        return None
    elif not flags.isValidFlagname(name):
        dialogs.warning(
            translate("FlagManager", "Invalid flagname"),
            translate("FlagManager", "This is not a valid flagname."), parent)
        return None

    return flags.addFlagType(name)
Ejemplo n.º 19
0
 def guessMetaContainers(self):
     byKey = {}
     for album in self.albums:
         name = ", ".join(album.tags[tags.TITLE])
         discstring = re.findall(self.metaRegex, name,flags=re.IGNORECASE)
         if len(discstring) > 0:
             discnumber = discstring[0]
             if discnumber.lower().startswith("i"): #roman number, support I-III :)
                 discnumber = len(discnumber)
             else:
                 discnumber = int(discnumber)
             discname_reduced = re.sub(self.metaRegex,"",name,flags=re.IGNORECASE)
             key = tuple( (tuple(album.tags[tag]) if tag in album.tags else None) for tag in self.groupTags[1:])
             if (key, discname_reduced) not in byKey:
                 byKey[(key, discname_reduced)] = {}
             if discnumber in byKey[(key,discname_reduced)]:
                 from maestro.gui.dialogs import warning
                 warning(self.tr("Error guessing meta-containers"),
                         self.tr("disc-number {} appears twice in meta-container {}").format(discnumber, key))
             else:
                 byKey[(key,discname_reduced)][discnumber] = album
     for key, contents in byKey.items():
         metaTags = tags.findCommonTags(contents.values())
         metaTags[tags.TITLE] = [key[1]]
         self.level.setTypes({album: ContainerType.Container for album in contents.values()})
         domain = next(iter(contents.values())).domain
         container = self.level.createContainer(domain=domain, tags=metaTags,
                                                contents=ContentList.fromPairs(contents.items()),
                                                type=ContainerType.Album)
         self.orders[container] = self.orders[contents[min(contents)]]
         self.albums.append(container)
         self.toplevels.add(container)
         for c in contents.values():
             if c in self.albums:
                 self.albums.remove(c)
             if c in self.toplevels:
                 self.toplevels.remove(c)
Ejemplo n.º 20
0
def export(profile):
    if profile.criterion is not None:
        engine = search.SearchEngine()
        request = engine.searchAndBlock(db.prefix+"elements", profile.criterion)
    else:
        raise NotImplementedError()
    print("Found {} elements for export".format(len(request.result)))
    if len(request.result) == 0:
        dialogs.warning(translate("wtf", "No elements found"),
                        translate("wtf", "The given filter criterion does not match any elements."))
        return False
    
    exported = set()
    if profile.structure == STRUCTURE_FLAT or profile.delete:
        exportedPaths = set()
    toExport = levels.real.collect(request.result)
    while len(toExport) > 0:
        element = toExport.pop()
        if element.id in exported:
            continue
        exported.add(element.id)
        if element.isContainer():
            toExport.extend(levels.real.collect(element.contents))
            continue
        if element.url.scheme != 'file':
            print("I can only export regular files. Skipping", str(element.url))
            continue
        
        if profile.structure == STRUCTURE_FLAT:
            exportPath = os.path.basename(element.url.path)
            if exportPath in exportedPaths:
                exportPath, ext = os.path.splitext(exportPath)
                i = 1
                while exportPath+"-" + str(i) + ext in exportedPaths:
                    i += 1
                exportPath += "-" + str(i) + ext 
            assert exportPath not in exportedPaths
            exportedPaths.add(exportPath)
        else:
            exportPath = element.url.path
            if profile.delete:
                exportedPaths.add(exportPath)
        
        src = os.path.join(config.options.main.collection, element.url.path)
        dest = os.path.join(profile.path, exportPath)
        os.makedirs(os.path.dirname(dest), exist_ok=True)
        shutil.copyfile(src, dest)
    
    if profile.delete:
        toDelete = []
        for dirPath, dirNames, fileNames in os.walk(profile.path):
            dirPath = os.path.relpath(dirPath, profile.path)
            if dirPath == '.':
                dirPath = ''
            for filePath in fileNames:
                filePath = os.path.join(dirPath, filePath)
                if filePath not in exportedPaths:
                    toDelete.append(os.path.join(profile.path, filePath))

        if len(toDelete) > 0 and \
                dialogs.question(translate("wtf", "Delete files?"),
                                 translate("wtf",
                                           "The target folder contains %n file(s) that have not been"
                                           " exported. Should they be deleted?",
                                           '', QtCore.QCoreApplication.CodecForTr, len(toDelete))):
            for filePath in toDelete:
                os.remove(filePath)
                # Delete empty directories
                dirPath = os.path.dirname(filePath)
                while len(os.listdir(dirPath)) == 0:
                    assert len(dirPath) > len(profile.path) # profile.path should never be empty
                    os.rmdir(dirPath)
                    dirPath = os.path.dirname(dirPath)
            
    return True
Ejemplo n.º 21
0
def buildFileTree(profile):
    if profile.criterion is not None:
        search.search(profile.criterion, profile.domain)
    else:
        raise NotImplementedError()
    result = profile.criterion.result
    profile.criterion.result = None # save memory when result is not needed anymore
    print("Found {} elements for export".format(len(result)))
    if len(result) == 0:
        dialogs.warning(translate("wtf", "No elements found"),
                        translate("wtf", "The given filter criterion does not match any elements."))
        return False
     
    fileTree = filetree.FileTreeModel()
    exported = set()
    if profile.structure == STRUCTURE_FLAT or OPTION_DELETE in profile.options:
        exportedPaths = set()
    toExport = levels.real.collect(result)
    while len(toExport) > 0:
        element = toExport.pop()
        if element.id in exported:
            continue
        exported.add(element.id)
        if element.isContainer():
            toExport.extend(levels.real.collect(element.contents))
            continue
        if element.url.scheme != 'file':
            print("I can only export regular files. Skipping", str(element.url))
            continue
        
        if profile.structure == STRUCTURE_FLAT:
            exportPath = os.path.basename(element.url.path)
            if exportPath in exportedPaths:
                exportPath, ext = os.path.splitext(exportPath)
                i = 1
                while exportPath+"-" + str(i) + ext in exportedPaths:
                    i += 1
                exportPath += "-" + str(i) + ext 
            assert exportPath not in exportedPaths
            exportedPaths.add(exportPath)
        else:
            exportPath = element.url.path
            source = filesystem.sourceByPath(exportPath)
            if source is not None:
                exportPath = source.relPath(exportPath)
            else:
                print("No source", exportPath)
            if OPTION_DELETE in profile.options:
                exportedPaths.add(exportPath)
        
        # Tag changes
        if OPTION_INCLUDE_WORK_TITLES in profile.options:
            titlesToAdd = []
            parentIds = element.parents
            while len(parentIds) > 0:
                parents = levels.real.collect(parentIds)
                parentIds = set() 
                for p in parents:
                    if p.type == elements.TYPE_WORK and tags.TITLE in p.tags:
                        titlesToAdd.extend(p.tags[tags.TITLE])
                    parentIds.update(p.parents)
            if len(titlesToAdd) > 0: 
                element = element.copy() # don't modify the element stored in levels.real
                element.tags[tags.TITLE] = [' - '.join(itertools.chain(reversed(titlesToAdd),
                                                                       element.tags[tags.TITLE]))]
        
        fileTree.addFile(exportPath, element)
    fileTree.sort()
    return fileTree
Ejemplo n.º 22
0
 def _handleError(self, error):
     dialogs.warning(self.tr("Tag write error"),
                     self.tr("An error ocurred: {}").format(error),
                     parent=self)
Ejemplo n.º 23
0
 def _guessHelper(self, files):
     files = list(files)
     domain = files[0].domain
     byKey = OrderedDict()
     existingParents = []
     pureDirMode = self.directoryMode and len(self.groupTags) == 0
     for element in files:
         self.orders[element] = self.currentOrder
         self.currentOrder += 1
         if len(element.parents) > 0:
             # there are already parents -> use the first one
             if element.parents[0] not in existingParents:
                 existingParents.append(element.parents[0])
         else:
             if pureDirMode:
                 key = os.path.dirname(element.url.path)
             else:
                 key = tuple( (tuple(element.tags[tag]) if tag in element.tags else None)
                                                        for tag in self.groupTags)
             if key not in byKey:
                 byKey[key] = []
             byKey[key].append(element)
     existing = self.level.collect(existingParents)
     for elem in existing:
         self.orders[elem] = self.currentOrder
         self.currentOrder += 1
     self.albums.extend(existing)
     self.toplevels.update(existing)
     for key, elements in byKey.items():
         flags = set()
         if self.compilationFlag is not None:
             for elem in elements:
                 if hasattr(elem, "specialTags") and "compilation" in elem.specialTags \
                                 and elem.specialTags["compilation"][0] not in ("0", ""):
                     flags.add(self.compilationFlag)
         if pureDirMode or (self.albumTag in elements[0].tags):
             def position(elem):
                 if hasattr(elem, "specialTags") and "tracknumber" in elem.specialTags:
                     return utils.parsePosition(elem.specialTags["tracknumber"][0])
                 return None
             elementsWithoutPos = { e for e in elements if position(e) is None }
             elementsWithPos = sorted(set(elements) - elementsWithoutPos, key = lambda e: position(e))
             children = {}
             for element in elementsWithPos:
                 if position(element) in children:
                     from maestro.gui.dialogs import warning
                     warning(self.tr("Error guessing albums"),
                             self.tr("position {} appears twice in {}").format(position(element), key))
                     self.level.removeElements([element])
                 else:
                     children[position(element)] = element.id
             firstFreePosition = position(elementsWithPos[-1])+1 if len(elementsWithPos) > 0 else 1
             for i, element in enumerate(elementsWithoutPos, start=firstFreePosition):
                 children[i] = element.id
             albumTags = tags.findCommonTags(elements)
             albumTags[tags.TITLE] = [key] if pureDirMode else elements[0].tags[self.albumTag]
             cType = ContainerType.Work if tags.get('composer') in albumTags else ContainerType.Album
             container = self.level.createContainer(domain=domain, tags=albumTags,
                                                    flags=list(flags), type=cType,
                                                    contents=ContentList.fromPairs(children.items()))
             self.orders[container] = self.orders[elements[0]]
             self.albums.append(container)
             self.toplevels.add(container)
         else:
             self.toplevels.update(elements)
Ejemplo n.º 24
0
 def insert(self, parent, position, wrappers, updateBackend='always'):
     """As in the inherited method, add *wrappers* at *position* into *parent*. But do this in a way
     that preserves a valid and if possible nice tree structure:
     
         - Call the treebuilder to add super containers to wrappers. When inserting into the rootnode
         this just makes nice trees, but otherwise it might be mandatory: When for example a file is
         inserted into a CD-box container the CD-container between has to be added.
         - Split the parent if the treebuilder returns wrappers that are not contained into it (e.g. when
         a file is inserted in the middle of an album to which it does not belong).
         - Glue the inserted wrappers with existing wrappers right before or after the insert position
         (e.g. when inserting a file next to its album container. It will be moved below the album).
          
     Return False if no element could be inserted (but not if *wrappers* is empty).
     """
     if len(wrappers) == 0:
         return True
     assert not parent.isFile()
     origWrappers = wrappers
     self.stack.beginMacro(self.tr("Insert elements"))
     
     # Build a tree
     preWrapper, postWrapper = self._getPrePostWrappers(parent, position)
     wrappers = treebuilder.buildTree(self.level, origWrappers, parent, preWrapper, postWrapper)
     
     # Check whether we have to split the parent because some wrappers do not fit into parent
     # It might be necessary to split several parents
     while parent is not self.root:
         if any(w.element.id not in parent.element.contents for w in wrappers):
             if position > 0:
                 self.split(parent, position)
                 # Now insert behind the first of the two parent nodes after the split. 
                 position = parent.parent.index(parent) + 1
             else: position = parent.parent.index(parent)
             parent = parent.parent
         else: break
     
     # If parent is the root node, remove toplevel wrappers with only one child from the tree generated
     # by the treebuilder.
     # But do not remove wrappers at the edges that will be glued in the next step
     # (this is also the reason why the treebuilder is allowed to return parents with only one child).
     # Also do not remove wrappers originally inserted by the user even if they are single parents.
     if parent is self.root:
         # We will remove single parents from wrappers[startPos:endPos] 
         startPos, endPos = 0, len(wrappers)
         if position > 0:
             preSibling = parent.contents[position-1]
             if preSibling.element.id == wrappers[0].element.id:
                 # First wrapper will be glued with preSibling => keep its parents
                 startPos += 1
         if position < len(parent.contents):
             postSibling = parent.contents[position]
             if postSibling.element.id == wrappers[-1].element.id:
                 # Last wrapper will be glued with postSibling => keep its parents
                 endPos -= 1
         for i in range(startPos, endPos):
             while wrappers[i].getContentsCount() == 1 and wrappers[i] not in origWrappers:
                 wrappers[i] = wrappers[i].contents[0]
     
     # Insert
     command = PlaylistInsertCommand(self, parent, position, wrappers, updateBackend)
     self.stack.push(command)
     if hasattr(command, 'error'):
         from maestro.gui import dialogs
         QtWidgets.qApp.setOverrideCursor(Qt.ArrowCursor)
         dialogs.warning(self.tr('Playlist error'), str(command.error))
         QtWidgets.qApp.restoreOverrideCursor()
         wrappers = command.wrappers
         if len(wrappers) == 0:
             self.stack.abortMacro()
             return False
         
     # Glue at the edges
     self.glue(parent, position+len(wrappers))
     self.glue(parent, position)
     
     self.stack.endMacro()
     return True
Ejemplo n.º 25
0
def export(profile):
    if profile.criterion is not None:
        engine = search.SearchEngine()
        request = engine.searchAndBlock(db.prefix + "elements",
                                        profile.criterion)
    else:
        raise NotImplementedError()
    print("Found {} elements for export".format(len(request.result)))
    if len(request.result) == 0:
        dialogs.warning(
            translate("wtf", "No elements found"),
            translate(
                "wtf",
                "The given filter criterion does not match any elements."))
        return False

    exported = set()
    if profile.structure == STRUCTURE_FLAT or profile.delete:
        exportedPaths = set()
    toExport = levels.real.collect(request.result)
    while len(toExport) > 0:
        element = toExport.pop()
        if element.id in exported:
            continue
        exported.add(element.id)
        if element.isContainer():
            toExport.extend(levels.real.collect(element.contents))
            continue
        if element.url.scheme != 'file':
            print("I can only export regular files. Skipping",
                  str(element.url))
            continue

        if profile.structure == STRUCTURE_FLAT:
            exportPath = os.path.basename(element.url.path)
            if exportPath in exportedPaths:
                exportPath, ext = os.path.splitext(exportPath)
                i = 1
                while exportPath + "-" + str(i) + ext in exportedPaths:
                    i += 1
                exportPath += "-" + str(i) + ext
            assert exportPath not in exportedPaths
            exportedPaths.add(exportPath)
        else:
            exportPath = element.url.path
            if profile.delete:
                exportedPaths.add(exportPath)

        src = os.path.join(config.options.main.collection, element.url.path)
        dest = os.path.join(profile.path, exportPath)
        os.makedirs(os.path.dirname(dest), exist_ok=True)
        shutil.copyfile(src, dest)

    if profile.delete:
        toDelete = []
        for dirPath, dirNames, fileNames in os.walk(profile.path):
            dirPath = os.path.relpath(dirPath, profile.path)
            if dirPath == '.':
                dirPath = ''
            for filePath in fileNames:
                filePath = os.path.join(dirPath, filePath)
                if filePath not in exportedPaths:
                    toDelete.append(os.path.join(profile.path, filePath))

        if len(toDelete) > 0 and \
                dialogs.question(translate("wtf", "Delete files?"),
                                 translate("wtf",
                                           "The target folder contains %n file(s) that have not been"
                                           " exported. Should they be deleted?",
                                           '', QtCore.QCoreApplication.CodecForTr, len(toDelete))):
            for filePath in toDelete:
                os.remove(filePath)
                # Delete empty directories
                dirPath = os.path.dirname(filePath)
                while len(os.listdir(dirPath)) == 0:
                    assert len(dirPath) > len(
                        profile.path)  # profile.path should never be empty
                    os.rmdir(dirPath)
                    dirPath = os.path.dirname(dirPath)

    return True
Ejemplo n.º 26
0
def buildFileTree(profile):
    if profile.criterion is not None:
        search.search(profile.criterion, profile.domain)
    else:
        raise NotImplementedError()
    result = profile.criterion.result
    profile.criterion.result = None  # save memory when result is not needed anymore
    print("Found {} elements for export".format(len(result)))
    if len(result) == 0:
        dialogs.warning(
            translate("wtf", "No elements found"),
            translate(
                "wtf",
                "The given filter criterion does not match any elements."))
        return False

    fileTree = filetree.FileTreeModel()
    exported = set()
    if profile.structure == STRUCTURE_FLAT or OPTION_DELETE in profile.options:
        exportedPaths = set()
    toExport = levels.real.collect(result)
    while len(toExport) > 0:
        element = toExport.pop()
        if element.id in exported:
            continue
        exported.add(element.id)
        if element.isContainer():
            toExport.extend(levels.real.collect(element.contents))
            continue
        if element.url.scheme != 'file':
            print("I can only export regular files. Skipping",
                  str(element.url))
            continue

        if profile.structure == STRUCTURE_FLAT:
            exportPath = os.path.basename(element.url.path)
            if exportPath in exportedPaths:
                exportPath, ext = os.path.splitext(exportPath)
                i = 1
                while exportPath + "-" + str(i) + ext in exportedPaths:
                    i += 1
                exportPath += "-" + str(i) + ext
            assert exportPath not in exportedPaths
            exportedPaths.add(exportPath)
        else:
            exportPath = element.url.path
            source = filesystem.sourceByPath(exportPath)
            if source is not None:
                exportPath = source.relPath(exportPath)
            else:
                print("No source", exportPath)
            if OPTION_DELETE in profile.options:
                exportedPaths.add(exportPath)

        # Tag changes
        if OPTION_INCLUDE_WORK_TITLES in profile.options:
            titlesToAdd = []
            parentIds = element.parents
            while len(parentIds) > 0:
                parents = levels.real.collect(parentIds)
                parentIds = set()
                for p in parents:
                    if p.type == elements.TYPE_WORK and tags.TITLE in p.tags:
                        titlesToAdd.extend(p.tags[tags.TITLE])
                    parentIds.update(p.parents)
            if len(titlesToAdd) > 0:
                element = element.copy(
                )  # don't modify the element stored in levels.real
                element.tags[tags.TITLE] = [
                    ' - '.join(
                        itertools.chain(reversed(titlesToAdd),
                                        element.tags[tags.TITLE]))
                ]

        fileTree.addFile(exportPath, element)
    fileTree.sort()
    return fileTree
Ejemplo n.º 27
0
 def _handleError(self, error):
     dialogs.warning(self.tr("Tag write error"),
                     self.tr("An error ocurred: {}").format(error),
                     parent=self)
Ejemplo n.º 28
0
 def _handleError(self, error):
     """Handle TagWriteErrors raised in methods of the model."""
     dialogs.warning(self.tr("Tag write error"),
                     self.tr("An error ocurred: {}").format(error),
                     parent=self)