def process(self, renamer, options): arg = renamer.currentArgument logging.msg('Processing "%s"' % (arg.path,), verbosity=3) d = defer.maybeDeferred(self.processArgument, arg) d.addCallback(self.buildDestination, options, arg) return d
def parseOptions(self): """ Parse configuration file and command-line options. """ _options = Options({}) _options.parseOptions() self._obs.verbosity = _options['verbosity'] self._configFile = config.ConfigFile( FilePath(os.path.expanduser(_options['config']))) command = self.getCommand(_options) options = Options(self._configFile) # Apply global defaults. options.update(self._configFile.get('renamer', options)) # Apply command-specific overrides for the global config. options.update( (k, v) for k, v in self._configFile.get(command.name, options).iteritems() if k in options) # Command-line options trump the config file. options.parseOptions() logging.msg( 'Global options: %r' % (options,), verbosity=5) return options
def makedirs(self, parent): """ Create any directory structure that does not yet exist. """ if not parent.exists(): logging.msg('Creating directory structure for "%s"' % ( parent.path,), verbosity=2) parent.makedirs()
def checkExisting(self, dst): """ Ensure that the destination file does not yet exist. """ if dst.exists(): msg = 'Refusing to clobber existing file "%s"' % ( dst.path,) logging.msg(msg) raise errors.NoClobber(msg)
def runCommand(self, command): """ Run a generic command. """ logging.msg( 'Using command "%s"' % (command.name,), verbosity=4) logging.msg( 'Command options: %r' % (command,), verbosity=5) return defer.maybeDeferred(command.process, self, self.options)
def lookupMetadata(self, seriesName, season, episode): """ Look up TV episode metadata on TVRage. """ url = self.buildURL(seriesName, season, episode) logging.msg('Looking up TVRage metadata at %s' % (url,), verbosity=4) d = self.agent.request('GET', url) d.addCallback(deliverBody, BodyReceiver) d.addCallback(self.extractMetadata) return d
def pruneActions(self): """ Remove any L{renamer.history.Action}s that do not have references to a L{renamer.history.Changeset}. These are actions most likely created and never used, so there is no need to store them. """ count = 0 for action in self.store.query(Action, Action.changeset == None): action.deleteFromStore() count += 1 logging.msg( 'Pruned %d actions' % (count,), verbosity=3) return count
def runRenamingCommand(self, command): """ Run a renaming command. """ def _processOne(src): self.currentArgument = src d = self.runCommand(command) d.addCallback(self.performRename, src) return d self.changeset = self.history.newChangeset() logging.msg( 'Running, doing at most %d concurrent operations' % ( self.options['concurrency'],), verbosity=3) return util.parallel( self.args, self.options['concurrency'], _processOne)
def pruneChangesets(self): """ Prune empty changesets from the currently active changesets. """ prunedChangesets = 0 prunedActions = self.pruneActions() for cs in self.store.query(Changeset): if not cs.numActions: cs.deleteFromStore() prunedChangesets += 1 elif cs.history is None: cs.history = self logging.msg( 'Pruned %d changesets' % (prunedChangesets,), verbosity=3) return prunedChangesets, prunedActions
def buildDestination(self, mapping, options, src): """ Build a destination path. Substitution of C{mapping} into the C{'prefix'} command-line option (defaulting to L{defaultPrefixTemplate}) and the C{'name'} command-line option (defaulting to L{defaultNameTemplate}) is performed. @type mapping: C{dict} mapping C{str} to C{unicode} @param mapping: Mapping of template variables, used for template substitution. @type options: C{dict} @type src: L{twisted.python.filepath.FilePath} @param src: Source path. @rtype: L{twisted.python.filepath.FilePath} @return: Destination path. """ prefixTemplate = options['prefix'] if prefixTemplate is None: prefixTemplate = self.defaultPrefixTemplate if prefixTemplate is not None: prefix = os.path.expanduser( prefixTemplate.safe_substitute(mapping)) else: prefixTemplate = string.Template(src.dirname()) prefix = prefixTemplate.template ext = src.splitext()[-1] nameTemplate = options['name'] if nameTemplate is None: nameTemplate = self.defaultNameTemplate filename = nameTemplate.safe_substitute(mapping) logging.msg( 'Building filename: prefix=%r name=%r mapping=%r' % ( prefixTemplate.template, nameTemplate.template, mapping), verbosity=3) return FilePath(prefix).child(filename).siblingExtension(ext)
def getTag(self, path, tagNames, default=u'UNKNOWN'): """ Get a metadata field by name. @type filename: L{twisted.python.filepath.FilePath} @type tagNames: C{list} of C{unicode} @param tagNames: A C{|} separated list of tag names to attempt when retrieving a value, the first successful result is returned @return: Tag value as C{unicode} or C{default} """ logging.msg('Getting metadata for %r from "%s"' % (tagNames, path.path), verbosity=4) md = self._getMetadata(path.path) for tagName in tagNames: try: return unicode(md[tagName][0]) except KeyError: pass return default
def performRename(self, dst, src): """ Perform a file rename. """ if self.options['no-act']: logging.msg('Simulating: %s => %s' % (src.path, dst.path)) return if src == dst: logging.msg('Skipping noop "%s"' % (src.path,), verbosity=2) return if self.options['link-dst']: self.changeset.do( self.changeset.newAction(u'symlink', src, dst), self.options) else: self.changeset.do( self.changeset.newAction(u'move', src, dst), self.options) if self.options['link-src']: self.changeset.do( self.changeset.newAction(u'symlink', dst, src), self.options)
def extractParts(self, filename, overrides=None): """ Get TV episode information from a filename. """ if overrides is None: overrides = {} rules = ['complete_strict', 'complete_lenient'] # We can only try the partial rules if there are some overrides. if filter(None, overrides.values()): rules.extend([ 'only_episode', 'partial_silly', 'only_series', 'only_episode_silly']) for rule in rules: g = FilenameGrammar(filename) logging.msg('Trying grammar rule "%s"' % (rule,), verbosity=5) try: res, err = g.apply(rule) except ParseError, e: try: logging.msg('Parsing error:', verbosity=5) for line in (e.formatError(filename).strip()).splitlines(): logging.msg(line, verbosity=5) except: pass continue else: series, (season, episode) = res parts = ( overrides.get('series') or series, overrides.get('season') or season, overrides.get('episode') or episode) if None not in parts: logging.msg('Found parts in "%s": %r' % (filename, parts), verbosity=4) return parts
def undo(self, options): if self.dst.islink(): logging.msg('Symlink: Removing %s' % (self.dst.path,)) self.dst.remove()
def do(self, options): self.prepare(self.dst, options) logging.msg('Symlink: %s => %s' % (self.src.path, self.dst.path)) self.src.linkTo(self.dst)
def _move(self, src, dst, options): self.prepare(dst, options) logging.msg('Move: %s => %s' % (src.path, dst.path)) util.rename(src, dst, oneFileSystem=options['one-file-system'])