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
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
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))
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()
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."))
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()
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."))
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()
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()
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)
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)
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()
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)
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
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()
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)
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)
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
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
def _handleError(self, error): dialogs.warning(self.tr("Tag write error"), self.tr("An error ocurred: {}").format(error), parent=self)
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)
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
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
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
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)