def modify(self, entry, config, validate=False, errors=True): """This can be called from a plugin to add set values to an entry""" # Create a new dict so we don't overwrite the set config with string replaced values. conf = copy(config) # Do jinja2 rendering/string replacement for field, value in conf.items(): if isinstance(value, basestring): logger = log.error if errors else log.debug result = replace_from_entry(value, entry, field, logger, default=None) if result is None: # If the replacement failed, remove this key from the update dict del conf[field] else: conf[field] = result if validate: from flexget import validator v = validator.factory('dict') for key in self.keys: v.accept(self.keys[key], key=key) if not v.validate(config): log.info('set parameters are invalid, error follows') log.info(v.errors.messages) return # If there are valid items in the config, apply to entry. if conf: log.debug('adding set: info to entry:\'%s\' %s' % (entry['title'], conf)) entry.update(conf)
def _make_torrent_options_dict(self, feed, entry): opt_dic = {} config = self.get_config(feed) for opt_key in ('path', 'addpaused', 'honourlimits', 'bandwidthpriority', 'maxconnections', 'maxupspeed', 'maxdownspeed', 'ratio'): if opt_key in entry: opt_dic[opt_key] = entry[opt_key] elif opt_key in config: opt_dic[opt_key] = config[opt_key] options = {'add': {}, 'change': {}} if 'path' in opt_dic: opt_dic['path'] = replace_from_entry(opt_dic['path'], entry, 'path', log.error) if opt_dic['path']: options['add']['download_dir'] = os.path.expanduser(opt_dic['path']) if 'addpaused' in opt_dic and opt_dic['addpaused']: options['add']['paused'] = True if 'bandwidthpriority' in opt_dic: options['add']['bandwidthPriority'] = opt_dic['bandwidthpriority'] if 'maxconnections' in opt_dic: options['add']['peer_limit'] = opt_dic['maxconnections'] if 'honourlimits' in opt_dic and not opt_dic['honourlimits']: options['change']['honorsSessionLimits'] = False if 'maxupspeed' in opt_dic: options['change']['uploadLimit'] = opt_dic['maxupspeed'] options['change']['uploadLimited'] = True if 'maxdownspeed' in opt_dic: options['change']['downloadLimit'] = opt_dic['maxdownspeed'] options['change']['downloadLimited'] = True if 'ratio' in opt_dic: options['change']['seedRatioLimit'] = opt_dic['ratio'] if opt_dic['ratio'] == -1: # seedRatioMode: # 0 follow the global settings # 1 override the global settings, seeding until a certain ratio # 2 override the global settings, seeding regardless of ratio options['change']['seedRatioMode'] = 2 else: options['change']['seedRatioMode'] = 1 return options
def execute(self, feed, phase_name): config = self.get_config(feed) if not phase_name in config: log.debug('phase %s not configured' % phase_name) return name_map = {'for_entries': feed.entries, 'for_accepted': feed.accepted, 'for_rejected': feed.rejected, 'for_failed': feed.failed} allow_background = config.get('allow_background') for operation, entries in name_map.iteritems(): if not operation in config[phase_name]: continue log.debug('running phase_name: %s operation: %s entries: %s' % (phase_name, operation, len(entries))) for entry in entries: cmd = config[phase_name][operation] entrydict = EscapingDict(entry) if config.get('auto_escape') else entry # Do string replacement from entry, but make sure quotes get escaped cmd = replace_from_entry(cmd, entrydict, 'exec command', log.error, default=None) if cmd is None: # fail the entry if configured to do so if config.get('fail_entries'): feed.fail(entry, 'Entry `%s` does not have required fields for string replacement.' % entry['title']) continue log.debug('phase_name: %s operation: %s cmd: %s' % (phase_name, operation, cmd)) if feed.manager.options.test: log.info('Would execute: %s' % cmd) else: # Run the command, fail entries with non-zero return code if configured to if self.execute_cmd(cmd, allow_background) != 0 and config.get('fail_entries'): feed.fail(entry, "exec return code was non-zero") # phase keyword in this if 'phase' in config[phase_name]: cmd = config[phase_name]['phase'] log.debug('phase cmd: %s' % cmd) if feed.manager.options.test: log.info('Would execute: %s' % cmd) else: self.execute_cmd(cmd, allow_background)
def output(self, feed, entry): """Moves temp-file into final destination Raises: PluginError if operation fails """ config = self.get_config(feed) if 'file' not in entry and not feed.manager.options.test: log.debug('file missing, entry: %s' % entry) raise PluginError('Entry `%s` has no temp file associated with' % entry['title']) try: # use path from entry if has one, otherwise use from download definition parameter path = entry.get('path', config.get('path')) if not isinstance(path, basestring): raise PluginError('Invalid `path` in entry `%s`' % entry['title']) # override path from command line parameter if feed.manager.options.dl_path: path = feed.manager.options.dl_path # expand variables in path path = replace_from_entry(path, entry, 'path', log.error) if not path: feed.fail(entry, 'Could not set path. Does not contain all fields for string replacement.') return path = os.path.expanduser(path) # If we are in test mode, report and return if feed.manager.options.test: log.info('Would write `%s` to `%s`' % (entry['title'], path)) # Set a fake location, so the exec plugin can do string replacement during --test #1015 entry['output'] = os.path.join(path, 'TEST_MODE_NO_OUTPUT') return # make path if not os.path.isdir(path): log.info('Creating directory %s' % path) try: os.makedirs(path) except: raise PluginError('Cannot create path %s' % path, log) # check that temp file is present if not os.path.exists(entry['file']): tmp_path = os.path.join(feed.manager.config_base, 'temp') log.debug('entry: %s' % entry) log.debug('temp: %s' % ', '.join(os.listdir(tmp_path))) raise PluginWarning('Downloaded temp file `%s` doesn\'t exist!?' % entry['file']) # if we still don't have a filename, try making one from title (last resort) if not entry.get('filename'): entry['filename'] = entry['title'] log.debug('set filename from title %s' % entry['filename']) if not 'mime-type' in entry: log.warning('Unable to figure proper filename for %s. Using title.' % entry['title']) else: guess = mimetypes.guess_extension(entry['mime-type']) if not guess: log.warning('Unable to guess extension with mime-type %s' % guess) else: self.filename_ext_from_mime(entry) # combine to full path + filename, replace / from filename (replaces bc tickets #208, #325, #353) name = entry.get('filename', entry['title']) for char in '/:<>^*?~"': name = name.replace(char, ' ') # remove duplicate spaces name = ' '.join(name.split()) destfile = os.path.join(path, name) log.debug('destfile: %s' % destfile) if os.path.exists(destfile): import filecmp if filecmp.cmp(entry['file'], destfile): log.debug("Identical destination file '%s' already exists", destfile) return elif config.get('overwrite'): log.debug("Overwriting already existing file %s" % destfile) else: log.info('File `%s` already exists and is not identical, download failed.' % destfile) feed.fail(entry, 'File `%s` already exists and is not identical.' % destfile) return # move temp file log.debug('moving %s to %s' % (entry['file'], destfile)) try: shutil.move(entry['file'], destfile) except OSError, err: # ignore permission errors, see ticket #555 import errno if not os.path.exists(destfile): raise PluginError('Unable to write %s' % destfile) if err.errno != errno.EPERM: raise # store final destination as output key entry['output'] = destfile
def on_get_session_state(torrent_ids): """Gets called with a list of torrent_ids loaded in the deluge session. Adds new torrents and modifies the settings for ones already in the session.""" dlist = [] # add the torrents for entry in feed.accepted: def add_entry(entry, opts): """Adds an entry to the deluge session""" magnet, filedump = None, None if entry.get('url', '').startswith('magnet:'): magnet = entry['url'] else: if not os.path.exists(entry['file']): feed.fail(entry, 'Downloaded temp file \'%s\' doesn\'t exist!' % entry['file']) del(entry['file']) return try: f = open(entry['file'], 'rb') filedump = base64.encodestring(f.read()) finally: f.close() log.debug('Adding %s to deluge.' % entry['title']) if magnet: return client.core.add_torrent_magnet(magnet, opts) else: return client.core.add_torrent_file(entry['title'], filedump, opts) # Generate deluge options dict for torrent add path = replace_from_entry(entry.get('path', config['path']), entry, 'path', log.error) add_opts = {} if path: add_opts['download_location'] = make_valid_path(os.path.expanduser(path)) for fopt, dopt in self.options.iteritems(): value = entry.get(fopt, config.get(fopt)) if value is not None: add_opts[dopt] = value if fopt == 'ratio': add_opts['stop_at_ratio'] = True # Make another set of options, that get set after the torrent has been added content_filename = entry.get('content_filename', config.get('content_filename', '')) movedone = replace_from_entry(entry.get('movedone', config['movedone']), entry, 'movedone', log.error) modify_opts = {'movedone': make_valid_path(os.path.expanduser(movedone)), 'label': format_label(entry.get('label', config['label'])), 'queuetotop': entry.get('queuetotop', config.get('queuetotop', None)), 'content_filename': replace_from_entry(content_filename, entry, 'content_filename', log.error), 'main_file_only': entry.get('main_file_only', config.get('main_file_only', False))} torrent_id = entry.get('deluge_id') or entry.get('torrent_info_hash') torrent_id = torrent_id and torrent_id.lower() if torrent_id in torrent_ids: log.info('%s is already loaded in deluge, setting options' % entry['title']) # Entry has a deluge id, verify the torrent is still in the deluge session and apply options # Since this is already loaded in deluge, we may also need to change the path modify_opts['path'] = add_opts.pop('download_location', None) dlist.extend([set_torrent_options(torrent_id, entry, modify_opts), client.core.set_torrent_options([torrent_id], add_opts)]) else: dlist.append(add_entry(entry, add_opts).addCallbacks(set_torrent_options, on_fail, callbackArgs=(entry, modify_opts), errbackArgs=(feed, entry))) return defer.DeferredList(dlist)
def add_to_deluge11(self, feed, config): """Add torrents to deluge using deluge 1.1.x api.""" try: from deluge.ui.client import sclient except: raise PluginError('Deluge module required', log) sclient.set_core_uri() for entry in feed.accepted: try: before = sclient.get_session_state() except Exception, (errno, msg): raise PluginError('Could not communicate with deluge core. %s' % msg, log) if feed.manager.options.test: return opts = {} path = entry.get('path', config['path']) if path: opts['download_location'] = os.path.expanduser(replace_from_entry(path, entry, 'path', logger=log.error)) for fopt, dopt in self.options.iteritems(): value = entry.get(fopt, config.get(fopt)) if value is not None: opts[dopt] = value if fopt == 'ratio': opts['stop_at_ratio'] = True # check that file is downloaded if not 'file' in entry: feed.fail(entry, 'file missing?') continue # see that temp file is present if not os.path.exists(entry['file']): tmp_path = os.path.join(feed.manager.config_base, 'temp') log.debug('entry: %s' % entry) log.debug('temp: %s' % ', '.join(os.listdir(tmp_path))) feed.fail(entry, 'Downloaded temp file \'%s\' doesn\'t exist!?' % entry['file']) continue sclient.add_torrent_file([entry['file']], [opts]) log.info('%s torrent added to deluge with options %s' % (entry['title'], opts)) movedone = entry.get('movedone', config['movedone']) label = entry.get('label', config['label']).lower() queuetotop = entry.get('queuetotop', config.get('queuetotop')) # Sometimes deluge takes a moment to add the torrent, wait a second. time.sleep(2) after = sclient.get_session_state() for item in after: # find torrentid of just added torrent if not item in before: movedone = replace_from_entry(movedone, entry, 'movedone', log.error) movedone = os.path.expanduser(movedone) if movedone: if not os.path.isdir(movedone): log.debug('movedone path %s doesn\'t exist, creating' % movedone) os.makedirs(movedone) log.debug('%s move on complete set to %s' % (entry['title'], movedone)) sclient.set_torrent_move_on_completed(item, True) sclient.set_torrent_move_on_completed_path(item, movedone) if label: if not 'label' in sclient.get_enabled_plugins(): sclient.enable_plugin('label') if not label in sclient.label_get_labels(): sclient.label_add(label) log.debug('%s label set to \'%s\'' % (entry['title'], label)) sclient.label_set_torrent(item, label) if queuetotop: log.debug('%s moved to top of queue' % entry['title']) sclient.queue_top([item]) break else: log.info('%s is already loaded in deluge. Cannot change label, movedone, or queuetotop' % entry['title'])