Repository: Flexget/Flexget

Issue:
from_deluge plugin KeyError with deluge 2.0
<!---
Before opening an issue, verify:

- Is this a feature request? Post it on https://feathub.com/Flexget/Flexget
- Did you recently upgrade? Look at the Change Log and Upgrade Actions to make sure that you don't need to make any changes to your config https://flexget.com/ChangeLog https://flexget.com/UpgradeActions
- Are you running FlexGet as a daemon? Stop it completely and then start it again https://flexget.com/CLI/daemon
- Did you search to see if the issue already exists? https://github.com/Flexget/Flexget/issues
- Did you fill out the issue template as completely as possible?

The issue template is here because it helps to ensure you submitted all the necessary information the first time, and allows us to more quickly review issues. Please fill it out correctly and do not ignore it, no matter how irrelevant you think it may be. Thanks in advance for your help with this!
--->
### Expected behaviour:
<!---
Please don't just say "it doesn't crash" or "it works". Explain what the expected result is.
--->
The from_deluge plugin should produce entries from the deluge client. 

### Actual behaviour:
KeyError, similar to #1086.
```
2018-07-31 20:54 CRITICAL task          remove_from_ram BUG: Unhandled error in plugin from_deluge: <class 'future.types.newlist.newlist'>
Traceback (most recent call last):
  File "/media/md5/superchicken/flexget-dev/flexget/task.py", line 486, in __run_plugin
    return method(*args, **kwargs)
  File "/media/md5/superchicken/flexget-dev/flexget/event.py", line 23, in __call__
    return self.func(*args, **kwargs)
  File "/media/md5/superchicken/flexget-dev/flexget/plugins/clients/deluge.py", line 214, in on_task_input
    self.entries = self.generate_entries(config)
  File "/media/md5/superchicken/flexget-dev/flexget/plugins/clients/deluge.py", line 220, in generate_entries
    torrents = self.get_torrents_status(list(self.settings_map.keys()) + config.get('keys', []))
  File "/media/md5/superchicken/flexget-dev/flexget/plugins/clients/deluge.py", line 71, in get_torrents_status
    return self.client.call('core.get_torrents_status', filters, fields)
  File "/media/md5/superchicken/pip/local/lib/python2.7/site-packages/deluge_client/client.py", line 213, in call
    self._send_call(self.deluge_version, method, *args, **kwargs)
  File "/media/md5/superchicken/pip/local/lib/python2.7/site-packages/deluge_client/client.py", line 130, in _send_call
    req = zlib.compress(dumps(req))
  File "/media/md5/superchicken/pip/local/lib/python2.7/site-packages/deluge_client/rencode.py", line 429, in dumps
    encode_func[type(x)](x, r)
  File "/media/md5/superchicken/pip/local/lib/python2.7/site-packages/deluge_client/rencode.py", line 379, in encode_list
    encode_func[type(i)](i, r)
  File "/media/md5/superchicken/pip/local/lib/python2.7/site-packages/deluge_client/rencode.py", line 379, in encode_list
    encode_func[type(i)](i, r)
  File "/media/md5/superchicken/pip/local/lib/python2.7/site-packages/deluge_client/rencode.py", line 379, in encode_list
    encode_func[type(i)](i, r)
KeyError: <class 'future.types.newlist.newlist'>
```
### Steps to reproduce:
- Use the from_deluge plugin with deluge 2.0+ 
- Use [deluge.py](https://github.com/Flexget/Flexget/blob/823f69bb8bdae5e0c8c3ffe1035240ec5901fafa/flexget/plugins/clients/deluge.py) from #2102 (needed for deluge 2.0+ support)

#### Config:
[config.yml](https://pastebin.com/fFVc17eW)
  
#### Log:
[crash_report.2018.07.31.205427858564.log](https://pastebin.com/sjfwtHGd)

### Additional information:

- FlexGet version: 2.14.11.dev
- Python version: 2.7.13
- Deluged version: 2.0.0b2.dev105 (libtorrent: 1.1.9.0)
- Deluge-client version: 1.6.0
- Installation method: 
  - build deluge/libtorrent/flexget from source and install to virtualenv
  - replace deluge.py with [this version ](https://github.com/Flexget/Flexget/blob/823f69bb8bdae5e0c8c3ffe1035240ec5901fafa/flexget/plugins/clients/deluge.py)from #2102
  - install deluge-client (`pip install deluge-client`)
- Using daemon (yes/no): yes
- OS and version: Debian GNU/Linux 9.4 (stretch)
- Link to crash log: [same as above](https://pastebin.com/sjfwtHGd)

<!---
In config and debug/crash logs, remember to redact any personal or sensitive information such as passwords, API keys, private URLs and so on.

Please verify that the following data is present before submitting your issue:

- Link to a paste service or paste above the relevant config (preferably full config, including templates if present). Please make sure the paste does not expire, if possible.
- Link to a paste service or paste above debug-level logs of the relevant task/s (use `flexget -L debug execute --tasks <Task_name>`).
- FlexGet version (use `flexget -V` to get it).
- Full Python version, for example `2.7.11` (use `python -V` to get it). Note that FlexGet is not supported for use with Python v3.0, 3.1, 3.2 or 3.6.
- Installation method (pip, git install, etc).
- Whether or not you're running FlexGet as a daemon.
- OS and version.
- Attach crash log if one was generated, in addition to the debug-level log. It can be found in the directory with your config file.
--->



Discussion:

Resolving patch:
diff --git a/flexget/_version.py b/flexget/_version.py
--- a/flexget/_version.py
+++ b/flexget/_version.py
@@ -7,4 +7,4 @@
 The jenkins release job will automatically strip the .dev for release,
 and update the version again for continued development.
 """
-__version__ = '2.16.4.dev'
+__version__ = '2.17.0.dev'
diff --git a/flexget/plugins/clients/deluge.py b/flexget/plugins/clients/deluge.py
--- a/flexget/plugins/clients/deluge.py
+++ b/flexget/plugins/clients/deluge.py
@@ -3,219 +3,92 @@
 from future.utils import native
 
 import base64
-import glob
-import logging
-import pkg_resources
-import os
 import re
 import sys
+
+import logging
+import os
+
 import time
-import warnings
 
 from flexget import plugin
 from flexget.entry import Entry
 from flexget.event import event
-from flexget.utils.template import RenderError
 from flexget.utils.pathscrub import pathscrub
+from flexget.utils.template import RenderError
 
 log = logging.getLogger('deluge')
 
 
-def add_deluge_windows_install_dir_to_sys_path():
-    # Deluge does not install to python system on Windows, add the install directory to sys.path if it is found
-    if not (sys.platform.startswith('win') or os.environ.get('ProgramFiles')):
-        return
-    deluge_dir = os.path.join(os.environ['ProgramFiles'], 'Deluge')
-    log.debug('Looking for deluge install in %s' % deluge_dir)
-    if not os.path.isdir(deluge_dir):
-        return
-    deluge_egg = glob.glob(os.path.join(deluge_dir, 'deluge-*-py2.?.egg'))
-    if not deluge_egg:
-        return
-    minor_version = int(re.search(r'py2\.(\d).egg', deluge_egg[0]).group(1))
-    if minor_version != sys.version_info[1]:
-        log.verbose('Cannot use deluge from install directory because its python version doesn\'t match.')
-        return
-    log.debug('Found deluge install in %s adding to sys.path' % deluge_dir)
-    sys.path.append(deluge_dir)
-    for item in os.listdir(deluge_dir):
-        if item.endswith(('.egg', '.zip')):
-            sys.path.append(os.path.join(deluge_dir, item))
-
-
-add_deluge_windows_install_dir_to_sys_path()
-
-
-def install_pausing_reactor():
-    class PausingReactor(SelectReactor):
-        """A SelectReactor that can be paused and resumed."""
-
-        def __init__(self):
-            SelectReactor.__init__(self)
-            self.paused = False
-            self._return_value = None
-            self._release_requested = False
-            self._mainLoopGen = None
-
-            # Older versions of twisted do not have the _started attribute, make it a synonym for running in that case
-            if not hasattr(self, '_started'):
-                PausingReactor._started = property(lambda self: self.running)
-
-        def _mainLoopGenerator(self):
-            """Generator that acts as mainLoop, but yields when requested."""
-            while self._started:
-                try:
-                    while self._started:
-                        if self._release_requested:
-                            self._release_requested = False
-                            self.paused = True
-                            yield self._return_value
-                            self.paused = False
-                        self.iterate()
-                except KeyboardInterrupt:
-                    # Keyboard interrupt pauses the reactor
-                    self.pause()
-                except GeneratorExit:
-                    # GeneratorExit means stop the generator; Do it cleanly by stopping the whole reactor.
-                    log.debug('Got GeneratorExit, stopping reactor.', exc_info=True)
-                    self.paused = False
-                    self.stop()
-                except Exception:
-                    twisted_log.msg("Unexpected error in main loop.")
-                    twisted_log.err()
-                else:
-                    twisted_log.msg('Main loop terminated.')
-
-        def run(self, installSignalHandlers=False):
-            """Starts or resumes the reactor."""
-            if not self._started:
-                self.startRunning(installSignalHandlers)
-                self._mainLoopGen = self._mainLoopGenerator()
-            try:
-                return next(self._mainLoopGen)
-            except StopIteration:
-                pass
-
-        def pause(self, return_value=None):
-            """Causes reactor to pause after this iteration.
-            If :return_value: is specified, it will be returned by the reactor.run call."""
-            self._return_value = return_value
-            self._release_requested = True
-
-        def stop(self):
-            """Stops the reactor."""
-            SelectReactor.stop(self)
-            # If this was called while the reactor was paused we have to resume in order for it to complete
-            if self.paused:
-                self.run()
-
-            # These need to be re-registered so that the PausingReactor can be safely restarted after a stop
-            self.addSystemEventTrigger('during', 'shutdown', self.crash)
-            self.addSystemEventTrigger('during', 'shutdown', self.disconnectAll)
-
-    # Configure twisted to use the PausingReactor.
-    installReactor(PausingReactor())
-
-    @event('manager.shutdown')
-    def stop_reactor(manager):
-        """Shut down the twisted reactor after all tasks have run."""
-        if not reactor._stopped:
-            log.debug('Stopping twisted reactor.')
-            reactor.stop()
-
-
-# Some twisted import is throwing a warning see #2434
-warnings.filterwarnings('ignore', message='Not importing directory .*')
-
-try:
-    from twisted.python import log as twisted_log
-    from twisted.internet.main import installReactor
-    from twisted.internet.selectreactor import SelectReactor
-except ImportError:
-    # If twisted is not found, errors will be shown later
-    pass
-else:
-    install_pausing_reactor()
-try:
-    # These have to wait until reactor has been installed to import
-    from twisted.internet import reactor
-    from deluge.ui.client import client
-    from deluge.ui.common import get_localhost_auth
-except (ImportError, pkg_resources.DistributionNotFound):
-    # If deluge is not found, errors will be shown later
-    pass
-
-
 class DelugePlugin(object):
     """Base class for deluge plugins, contains settings and methods for connecting to a deluge daemon."""
 
     def on_task_start(self, task, config):
         """Raise a DependencyError if our dependencies aren't available"""
         try:
-            from deluge.ui.client import client  # noqa
+            from deluge_client import DelugeRPCClient
         except ImportError as e:
-            log.debug('Error importing deluge: %s' % e)
-            raise plugin.DependencyError('deluge', 'deluge',
-                                         'Deluge >=1.2 module and it\'s dependencies required. ImportError: %s' % e,
+            log.debug('Error importing deluge-client: %s' % e)
+            raise plugin.DependencyError('deluge', 'deluge-client',
+                                         'deluge-client >=1.5 is required. `pip install deluge-client` to install.',
                                          log)
-        try:
-            from twisted.internet import reactor  # noqa
-        except:
-            raise plugin.DependencyError('deluge', 'twisted.internet', 'Twisted.internet package required', log)
+        config = self.prepare_config(config)
+
+        if config['host'] in ['localhost', '127.0.0.1'] and not config.get('username'):
+            # If an username is not specified, we have to do a lookup for the localclient username/password
+            auth = self.get_localhost_auth()
+            if auth and auth[0]:
+                config['username'], config['password'] = auth
+            else:
+                raise plugin.PluginError('Unable to get local authentication info for Deluge. You may need to '
+                                         'specify an username and password from your Deluge auth file.')
+
+        self.client = DelugeRPCClient(config['host'], config['port'], config['username'], config['password'],
+                                      decode_utf8=True)
 
     def on_task_abort(self, task, config):
         pass
 
-    def prepare_connection_info(self, config):
+    def prepare_config(self, config):
         config.setdefault('host', 'localhost')
         config.setdefault('port', 58846)
-        if 'user' in config or 'pass' in config:
-            warnings.warn('deluge `user` and `pass` options have been renamed `username` and `password`',
-                          DeprecationWarning)
-            config.setdefault('username', config.get('user', ''))
-            config.setdefault('password', config.get('pass', ''))
-        config.setdefault('username', '')
-        config.setdefault('password', '')
-
-    def on_disconnect(self):
-        """Pauses the reactor. Gets called when we disconnect from the daemon."""
-        # pause the reactor, so flexget can continue
-        reactor.callLater(0, reactor.pause)
-
-    def on_connect_fail(self, result):
-        """Pauses the reactor, returns PluginError. Gets called when connection to deluge daemon fails."""
-        log.debug('Connect to deluge daemon failed, result: %s' % result)
-        reactor.callLater(0, reactor.pause, plugin.PluginError('Could not connect to deluge daemon', log))
-
-    def on_connect_success(self, result, task, config):
-        """Gets called when successfully connected to the daemon. Should do the work then call client.disconnect"""
-        raise NotImplementedError
-
-    def connect(self, task, config):
+        return config
+
+    def connect(self):
         """Connects to the deluge daemon and runs on_connect_success """
 
-        if config['host'] in ['localhost', '127.0.0.1'] and not config.get('username'):
-            # If an username is not specified, we have to do a lookup for the localclient username/password
-            auth = get_localhost_auth()
-            if auth[0]:
-                config['username'], config['password'] = auth
-            else:
-                raise plugin.PluginError('Unable to get local authentication info for Deluge. You may need to '
-                                         'specify an username and password from your Deluge auth file.')
+        self.client.connect()
+
+        if not self.client.connected:
+            raise plugin.PluginError('Deluge failed to connect.')
+
+    def disconnect(self):
+        self.client.disconnect()
+
+    def get_torrents_status(self, fields, filters=None):
+        """Fetches all torrents and their requested fields optionally filtered"""
+        if filters is None:
+            filters = {}
+        return self.client.call('core.get_torrents_status', filters, fields)
 
-        client.set_disconnect_callback(self.on_disconnect)
+    @staticmethod
+    def get_localhost_auth():
+        if sys.platform.startswith('win'):
+            auth_file = os.path.join(os.getenv('APPDATA'), 'deluge', 'auth')
+        else:
+            auth_file = os.path.expanduser('~/.config/deluge/auth')
 
-        d = client.connect(
-            host=config['host'],
-            port=config['port'],
-            username=config['username'],
-            password=config['password'])
+        with open(auth_file) as auth:
+            for line in auth:
+                line = line.strip()
+                if line.startswith('#') or not line:
+                    # This is a comment or blank line
+                    continue
 
-        d.addCallback(self.on_connect_success, task, config).addErrback(self.on_connect_fail)
-        result = reactor.run()
-        if isinstance(result, Exception):
-            raise result
-        return result
+                lsplit = line.split(':')
+                if lsplit[0] == 'localclient':
+                    username, password = lsplit[:2]
+                    return username, password
 
 
 class InputDeluge(DelugePlugin):
@@ -268,9 +141,9 @@ class InputDeluge(DelugePlugin):
         'total_payload_download': 'total_payload_download',
         'total_payload_upload': 'total_payload_upload',
         'total_peers': 'total_peers',
-        'total_seeds': 'total_seeds', 
+        'total_seeds': 'total_seeds',
         'total_uploaded': 'total_uploaded',
-        'total_wanted': 'total_wanted', 
+        'total_wanted': 'total_wanted',
         'tracker': 'tracker',
         'tracker_host': 'tracker_host',
         'tracker_status': 'tracker_status',
@@ -316,6 +189,10 @@ def __init__(self):
         ]
     }
 
+    def on_task_start(self, task, config):
+        config = self.prepare_config(config)
+        super(InputDeluge, self).on_task_start(task, config)
+
     def prepare_config(self, config):
         if isinstance(config, bool):
             config = {}
@@ -325,7 +202,7 @@ def prepare_config(self, config):
                 filter['label'] = filter['label'].lower()
             if 'state' in filter:
                 filter['state'] = filter['state'].capitalize()
-        self.prepare_connection_info(config)
+        super(InputDeluge, self).prepare_config(config)
         return config
 
     def on_task_input(self, task, config):
@@ -333,44 +210,42 @@ def on_task_input(self, task, config):
         # Reset the entries list
         self.entries = []
         # Call connect, entries get generated if everything is successful
-        self.connect(task, self.prepare_config(config))
-        return self.entries
-
-    def on_connect_success(self, result, task, config):
-        """Creates a list of FlexGet entries from items loaded in deluge and stores them to self.entries"""
-        from deluge.ui.client import client
+        self.connect()
 
-        def on_get_torrents_status(torrents):
-            config_path = os.path.expanduser(config.get('config_path', ''))
-            for hash, torrent_dict in torrents.items():
-                # Make sure it has a url so no plugins crash
-                entry = Entry(deluge_id=hash, url='')
-                if config_path:
-                    torrent_path = os.path.join(config_path, 'state', hash + '.torrent')
-                    if os.path.isfile(torrent_path):
-                        entry['location'] = torrent_path
-                        if not torrent_path.startswith('/'):
-                            torrent_path = '/' + torrent_path
-                        entry['url'] = 'file://' + torrent_path
-                    else:
-                        log.warning('Did not find torrent file at %s' % torrent_path)
-                for key, value in torrent_dict.items():
-                    if key in self.settings_map:
-                        flexget_key = self.settings_map[key]
-                    else:
-                        flexget_key = self.extra_settings_map[key]
-                    if isinstance(flexget_key, tuple):
-                        flexget_key, format_func = flexget_key
-                        value = format_func(value)
-                    entry[flexget_key] = value
-                self.entries.append(entry)
-            client.disconnect()
+        self.entries = self.generate_entries(config)
+        self.disconnect()
+        return self.entries
 
+    def generate_entries(self, config):
+        entries = []
         filter = config.get('filter', {})
-
         # deluge client lib chokes on future's newlist, make sure we have a native python list here
-        client.core.get_torrents_status(filter, native(list(self.settings_map.keys()) + config.get('keys', []))).addCallback(
-            on_get_torrents_status)
+        torrents = self.get_torrents_status(native(list(self.settings_map.keys())) + config.get('keys', []), filter)
+        for hash, torrent_dict in torrents.items():
+            # Make sure it has a url so no plugins crash
+            entry = Entry(deluge_id=hash, url='')
+            config_path = os.path.expanduser(config.get('config_path', ''))
+            if config_path:
+                torrent_path = os.path.join(config_path, 'state', hash + '.torrent')
+                if os.path.isfile(torrent_path):
+                    entry['location'] = torrent_path
+                    if not torrent_path.startswith('/'):
+                        torrent_path = '/' + torrent_path
+                    entry['url'] = 'file://' + torrent_path
+                else:
+                    log.warning('Did not find torrent file at %s', torrent_path)
+            for key, value in torrent_dict.items():
+                if key in self.settings_map:
+                    flexget_key = self.settings_map[key]
+                else:
+                    flexget_key = self.extra_settings_map[key]
+                if isinstance(flexget_key, tuple):
+                    flexget_key, format_func = flexget_key
+                    value = format_func(value)
+                entry[flexget_key] = value
+            entries.append(entry)
+
+        return entries
 
 
 class OutputDeluge(DelugePlugin):
@@ -415,7 +290,7 @@ class OutputDeluge(DelugePlugin):
     def prepare_config(self, config):
         if isinstance(config, bool):
             config = {'enabled': config}
-        self.prepare_connection_info(config)
+        super(OutputDeluge, self).prepare_config(config)
         config.setdefault('enabled', True)
         config.setdefault('path', '')
         config.setdefault('movedone', '')
@@ -439,7 +314,6 @@ def on_task_download(self, task, config):
         Call download plugin to generate the temp files we will load into deluge
         then verify they are valid torrents
         """
-        import deluge.ui.common
         config = self.prepare_config(config)
         if not config['enabled']:
             return
@@ -450,16 +324,6 @@ def on_task_download(self, task, config):
                 if not entry.get('deluge_id'):
                     download.instance.get_temp_file(task, entry, handle_magnets=True)
 
-        # Check torrent files are valid
-        for entry in task.accepted:
-            if os.path.exists(entry.get('file', '')):
-                # Check if downloaded file is a valid torrent file
-                try:
-                    deluge.ui.common.TorrentInfo(entry['file'])
-                except Exception:
-                    entry.fail('Invalid torrent file')
-                    log.error('Torrent file appears invalid for: %s', entry['title'])
-
     @plugin.priority(135)
     def on_task_output(self, task, config):
         """Add torrents to deluge at exit."""
@@ -470,401 +334,135 @@ def on_task_output(self, task, config):
         if not config['enabled'] or not (task.accepted or task.options.test):
             return
 
-        self.connect(task, config)
-        # Clean up temp file if download plugin is not configured for this task
-        if 'download' not in task.config:
-            for entry in task.accepted + task.failed:
-                if os.path.exists(entry.get('file', '')):
-                    os.remove(entry['file'])
-                    del (entry['file'])
-
-    def on_connect_success(self, result, task, config):
-        """Gets called when successfully connected to a daemon."""
-        from deluge.ui.client import client
-        from twisted.internet import reactor, defer
-
-        if not result:
-            log.debug('on_connect_success returned a failed result. BUG?')
+        self.connect()
 
         if task.options.test:
             log.debug('Test connection to deluge daemon successful.')
-            client.disconnect()
+            self.client.disconnect()
             return
 
-        def format_label(label):
-            """Makes a string compliant with deluge label naming rules"""
-            return re.sub('[^\w-]+', '_', label.lower())
-
-        def set_torrent_options(torrent_id, entry, opts):
-            """Gets called when a torrent was added to the daemon."""
-            dlist = []
-            if not torrent_id:
-                log.error('There was an error adding %s to deluge.' % entry['title'])
-                # TODO: Fail entry? How can this happen still now?
-                return
-            log.info('%s successfully added to deluge.' % entry['title'])
-            entry['deluge_id'] = torrent_id
-
-            def create_path(result, path):
-                """Creates the specified path if deluge is older than 1.3"""
-                from deluge.common import VersionSplit
-                # Before 1.3, deluge would not create a non-existent move directory, so we need to.
-                if VersionSplit('1.3.0') > VersionSplit(self.deluge_version):
-                    if client.is_localhost():
-                        if not os.path.isdir(path):
-                            log.debug('path %s doesn\'t exist, creating' % path)
-                            os.makedirs(path)
-                    else:
-                        log.warning('If path does not exist on the machine running the daemon, move will fail.')
-
-            if opts.get('movedone'):
-                dlist.append(version_deferred.addCallback(create_path, opts['movedone']))
-                dlist.append(client.core.set_torrent_move_completed(torrent_id, True))
-                dlist.append(client.core.set_torrent_move_completed_path(torrent_id, opts['movedone']))
-                log.debug('%s move on complete set to %s' % (entry['title'], opts['movedone']))
-            if opts.get('label'):
-                def apply_label(result, torrent_id, label):
-                    """Gets called after labels and torrent were added to deluge."""
-                    return client.label.set_torrent(torrent_id, label)
-
-                dlist.append(label_deferred.addCallback(apply_label, torrent_id, opts['label']))
-            if opts.get('queuetotop') is not None:
-                if opts['queuetotop']:
-                    dlist.append(client.core.queue_top([torrent_id]))
-                    log.debug('%s moved to top of queue' % entry['title'])
-                else:
-                    dlist.append(client.core.queue_bottom([torrent_id]))
-                    log.debug('%s moved to bottom of queue' % entry['title'])
-
-            def on_get_torrent_status(status):
-                """Gets called with torrent status, including file info.
-                Sets the torrent options which require knowledge of the current status of the torrent."""
-
-                main_file_dlist = []
-
-                # Determine where the file should be
-                move_now_path = None
-                if opts.get('movedone'):
-                    if status['progress'] == 100:
-                        move_now_path = opts['movedone']
-                    else:
-                        # Deluge will unset the move completed option if we move the storage, forgo setting proper
-                        # path, in favor of leaving proper final location.
-                        log.debug('Not moving storage for %s, as this will prevent movedone.' % entry['title'])
-                elif opts.get('path'):
-                    move_now_path = opts['path']
-
-                if move_now_path and os.path.normpath(move_now_path) != os.path.normpath(status['save_path']):
-                    main_file_dlist.append(version_deferred.addCallback(create_path, move_now_path))
-                    log.debug('Moving storage for %s to %s' % (entry['title'], move_now_path))
-                    main_file_dlist.append(client.core.move_storage([torrent_id], move_now_path))
-
-                big_file_name = ''
-                if opts.get('content_filename') or opts.get('main_file_only'):
-
-                    def file_exists(filename):
-                        # Checks the download path as well as the move completed path for existence of the file
-                        if os.path.exists(os.path.join(status['save_path'], filename)):
-                            return True
-                        elif status.get('move_on_completed') and status.get('move_on_completed_path'):
-                            if os.path.exists(os.path.join(status['move_on_completed_path'], filename)):
-                                return True
-                        else:
-                            return False
-
-                    def unused_name(name):
-                        # If on local computer, tries appending a (#) suffix until a unique filename is found
-                        if client.is_localhost():
-                            counter = 2
-                            while file_exists(name):
-                                name = ''.join([os.path.splitext(name)[0],
-                                                " (", str(counter), ')',
-                                                os.path.splitext(name)[1]])
-                                counter += 1
-                        else:
-                            log.debug('Cannot ensure content_filename is unique '
-                                      'when adding to a remote deluge daemon.')
-                        return name
-
-                    def rename(file, new_name):
-                        # Renames a file in torrent
-                        main_file_dlist.append(
-                            client.core.rename_files(torrent_id,
-                                                     [(file['index'], new_name)]))
-                        log.debug('File %s in %s renamed to %s' % (file['path'], entry['title'], new_name))
-
-                    # find a file that makes up more than main_file_ratio (default: 90%) of the total size
-                    main_file = None
-                    for file in status['files']:
-                        if file['size'] > (status['total_size'] * opts.get('main_file_ratio')):
-                            main_file = file
-                            break
-
-                    if main_file is not None:
-                        # proceed with renaming only if such a big file is found
-
-                        # find the subtitle file
-                        keep_subs = opts.get('keep_subs')
-                        sub_file = None
-                        if keep_subs:
-                            sub_exts = [".srt", ".sub"]
-                            for file in status['files']:
-                                ext = os.path.splitext(file['path'])[1]
-                                if ext in sub_exts:
-                                    sub_file = file
-                                    break
-
-                        # check for single file torrents so we dont add unnecessary folders
-                        if (os.path.dirname(main_file['path']) is not ("" or "/")):
-                            # check for top folder in user config
-                            if (opts.get('content_filename') and os.path.dirname(opts['content_filename']) is not ""):
-                                top_files_dir = os.path.dirname(opts['content_filename']) + "/"
-                            else:
-                                top_files_dir = os.path.dirname(main_file['path']) + "/"
-                        else:
-                            top_files_dir = "/"
-
-                        if opts.get('content_filename'):
-                            # rename the main file
-                            big_file_name = (top_files_dir +
-                                             os.path.basename(opts['content_filename']) +
-                                             os.path.splitext(main_file['path'])[1])
-                            big_file_name = unused_name(big_file_name)
-                            rename(main_file, big_file_name)
-
-                            # rename subs along with the main file
-                            if sub_file is not None and keep_subs:
-                                sub_file_name = (os.path.splitext(big_file_name)[0] +
-                                                 os.path.splitext(sub_file['path'])[1])
-                                rename(sub_file, sub_file_name)
-
-                        if opts.get('main_file_only'):
-                            # download only the main file (and subs)
-                            file_priorities = [1 if f == main_file or (f == sub_file and keep_subs) else 0
-                                               for f in status['files']]
-                            main_file_dlist.append(
-                                client.core.set_torrent_file_priorities(torrent_id, file_priorities))
-
-                            if opts.get('hide_sparse_files'):
-                                # hide the other sparse files that are not supposed to download but are created anyway
-                                # http://dev.deluge-torrent.org/ticket/1827
-                                # Made sparse files behave better with deluge http://flexget.com/ticket/2881
-                                sparse_files = [f for f in status['files']
-                                                if f != main_file and (f != sub_file or (not keep_subs))]
-                                rename_pairs = [(f['index'],
-                                                 top_files_dir + ".sparse_files/" + os.path.basename(f['path']))
-                                                for f in sparse_files]
-                                main_file_dlist.append(client.core.rename_files(torrent_id, rename_pairs))
-                    else:
-                        log.warning('No files in "%s" are > %d%% of content size, no files renamed.' % (
-                            entry['title'],
-                            opts.get('main_file_ratio') * 100))
-
-                container_directory =  pathscrub(entry.render(entry.get('container_directory', config.get('container_directory', ''))))
-                if container_directory:
-                    if big_file_name:
-                        folder_structure = big_file_name.split(os.sep)
-                    elif len(status['files']) > 0:
-                        folder_structure = status['files'][0]['path'].split(os.sep)
-                    else:
-                        folder_structure = []
-                    if len(folder_structure) > 1:
-                        log.verbose('Renaming Folder %s to %s', folder_structure[0], container_directory)
-                        main_file_dlist.append(client.core.rename_folder(torrent_id, folder_structure[0], container_directory))
-                    else:
-                        log.debug('container_directory specified however the torrent %s does not have a directory structure; skipping folder rename', entry['title'])
-
-                return defer.DeferredList(main_file_dlist)
-
-            status_keys = ['files', 'total_size', 'save_path', 'move_on_completed_path',
-                           'move_on_completed', 'progress']
-            dlist.append(client.core.get_torrent_status(torrent_id, status_keys).addCallback(on_get_torrent_status))
-
-            return defer.DeferredList(dlist)
-
-        def on_fail(result, task, entry):
-            """Gets called when daemon reports a failure adding the torrent."""
-            log.info('%s was not added to deluge! %s' % (entry['title'], result))
-            entry.fail('Could not be added to deluge')
-
-        # dlist is a list of deferreds that must complete before we exit
-        dlist = []
         # loop through entries to get a list of labels to add
         labels = set()
         for entry in task.accepted:
-            if entry.get('label', config.get('label')):
+            label = entry.get('label', config.get('label'))
+            if label:
                 try:
-                    label = format_label(entry.render(entry.get('label', config.get('label'))))
+                    label = self._format_label(entry.render(entry.get('label', config.get('label'))))
                     log.debug('Rendered label: %s', label)
                 except RenderError as e:
                     log.error('Error rendering label `%s`: %s', label, e)
                     continue
                 labels.add(label)
-        label_deferred = defer.succeed(True)
         if labels:
             # Make sure the label plugin is available and enabled, then add appropriate labels
 
-            def on_get_enabled_plugins(plugins):
-                """Gets called with the list of enabled deluge plugins."""
-
-                def on_label_enabled(result):
-                    """ This runs when we verify the label plugin is enabled. """
-
-                    def on_get_labels(d_labels):
-                        """Gets available labels from deluge, and adds any new labels we need."""
-                        dlist = []
-                        for label in labels:
-                            if label not in d_labels:
-                                log.debug('Adding the label `%s` to deluge', label)
-                                dlist.append(client.label.add(label))
-                        return defer.DeferredList(dlist)
+            enabled_plugins = self.client.call('core.get_enabled_plugins')
+            label_enabled = 'Label' in enabled_plugins
+            if not label_enabled:
+                available_plugins = self.client.call('core.get_available_plugins')
+                if 'Label' in available_plugins:
+                    log.debug('Enabling label plugin in deluge')
+                    label_enabled = self.client.call('core.enable_plugin', 'Label')
+                else:
+                    log.error('Label plugin is not installed in deluge')
 
-                    return client.label.get_labels().addCallback(on_get_labels)
+            if label_enabled:
+                d_labels = self.client.call('label.get_labels')
+                for label in labels:
+                    if label not in d_labels:
+                        log.debug('Adding the label `%s` to deluge', label)
+                        self.client.call('label.add', label)
 
-                if 'Label' in plugins:
-                    return on_label_enabled(True)
+        # add the torrents
+        torrent_ids = self.client.call('core.get_session_state')
+        for entry in task.accepted:
+            # Generate deluge options dict for torrent add
+            add_opts = {}
+            try:
+                path = entry.render(entry.get('path', config['path']))
+                if path:
+                    add_opts['download_location'] = pathscrub(os.path.expanduser(path))
+            except RenderError as e:
+                log.error('Could not set path for %s: %s', entry['title'], e)
+            for fopt, dopt in self.options.items():
+                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
+            modify_opts = {
+                'queuetotop': entry.get('queuetotop', config.get('queuetotop')),
+                'main_file_only': entry.get('main_file_only', config.get('main_file_only', False)),
+                'main_file_ratio': entry.get('main_file_ratio', config.get('main_file_ratio')),
+                'hide_sparse_files': entry.get('hide_sparse_files', config.get('hide_sparse_files', True)),
+                'keep_subs': entry.get('keep_subs', config.get('keep_subs', True)),
+                'container_directory': config.get('container_directory', '')
+            }
+            try:
+                label = entry.render(entry.get('label', config['label']))
+                modify_opts['label'] = self._format_label(label)
+            except RenderError as e:
+                log.error('Error setting label for `%s`: %s', entry['title'], e)
+            try:
+                movedone = entry.render(entry.get('movedone', config['movedone']))
+                modify_opts['movedone'] = pathscrub(os.path.expanduser(movedone))
+            except RenderError as e:
+                log.error('Error setting movedone for %s: %s', entry['title'], e)
+            try:
+                content_filename = entry.get('content_filename', config.get('content_filename', ''))
+                modify_opts['content_filename'] = pathscrub(entry.render(content_filename))
+            except RenderError as e:
+                log.error('Error setting content_filename for %s: %s', entry['title'], e)
+
+            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)
+                self.client.call('core.set_torrent_options', [torrent_id], add_opts)
+                self._set_torrent_options(torrent_id, entry, modify_opts)
+            else:
+                magnet, filedump = None, None
+                if entry.get('url', '').startswith('magnet:'):
+                    magnet = entry['url']
                 else:
-                    # Label plugin isn't enabled, so we check if it's available and enable it.
-
-                    def on_get_available_plugins(plugins):
-                        """Gets plugins available to deluge, enables Label plugin if available."""
-                        if 'Label' in plugins:
-                            log.debug('Enabling label plugin in deluge')
-                            return client.core.enable_plugin('Label').addCallback(on_label_enabled)
+                    if not os.path.exists(entry['file']):
+                        entry.fail('Downloaded temp file \'%s\' doesn\'t exist!' % entry['file'])
+                        del (entry['file'])
+                        return
+                    with open(entry['file'], 'rb') as f:
+                        filedump = base64.encodestring(f.read())
+
+                log.verbose('Adding %s to deluge.', entry['title'])
+                added_torrent = None
+                if magnet:
+                    added_torrent = self.client.call('core.add_torrent_magnet', magnet, add_opts)
+                    if config.get('magnetization_timeout'):
+                        timeout = config['magnetization_timeout']
+                        log.verbose('Waiting %d seconds for "%s" to magnetize', timeout, entry['title'])
+                        for _ in range(timeout):
+                            time.sleep(1)
+                            try:
+                                status = self.client.call('core.get_torrent_status', torrent_id, ['files'])
+                            except Exception as err:
+                                log.error('wait_for_metadata Error: %s', err)
+                                break
+                            if status.get('files'):
+                                log.info('"%s" magnetization successful', entry['title'])
+                                break
                         else:
-                            log.error('Label plugin is not installed in deluge')
-
-                    return client.core.get_available_plugins().addCallback(on_get_available_plugins)
-
-            label_deferred = client.core.get_enabled_plugins().addCallback(on_get_enabled_plugins)
-            dlist.append(label_deferred)
-
-        def on_get_daemon_info(ver):
-            """Gets called with the daemon version info, stores it in self."""
-            log.debug('deluge version %s' % ver)
-            self.deluge_version = ver
-
-        version_deferred = client.daemon.info().addCallback(on_get_daemon_info)
-        dlist.append(version_deferred)
-
-        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 task.accepted:
-
-                @defer.inlineCallbacks
-                def _wait_for_metadata(torrent_id, timeout):
-                    log.verbose('Waiting %d seconds for "%s" to magnetize' % (timeout, entry['title']))
-                    for _ in range(timeout):
-                        time.sleep(1)
-                        try:
-                            status = yield client.core.get_torrent_status(torrent_id, ['files'])
-                        except Exception as err:
-                            log.error('wait_for_metadata Error: %s' % err)
-                            break
-                        if status.get('files'):
-                            log.info('"%s" magnetization successful' % (entry['title']))
-                            break
-                    else:
-                        log.warning('"%s" did not magnetize before the timeout elapsed, '
-                                    'file list unavailable for processing.' % entry['title'])
-
-                    defer.returnValue(torrent_id)
-
-                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']):
-                            entry.fail('Downloaded temp file \'%s\' doesn\'t exist!' % entry['file'])
-                            del (entry['file'])
-                            return
-                        with open(entry['file'], 'rb') as f:
-                            filedump = base64.encodestring(f.read())
-
-                    log.verbose('Adding %s to deluge.' % entry['title'])
-                    if magnet:
-                        d = client.core.add_torrent_magnet(magnet, opts)
-                        if config.get('magnetization_timeout'):
-                            d.addCallback(_wait_for_metadata, config['magnetization_timeout'])
-                        return d
-                    else:
-                        return client.core.add_torrent_file(entry['title'], filedump, opts)
-
-                # Generate deluge options dict for torrent add
-                add_opts = {}
-                try:
-                    path = entry.render(entry.get('path', config['path']))
-                    if path:
-                        add_opts['download_location'] = pathscrub(os.path.expanduser(path))
-                except RenderError as e:
-                    log.error('Could not set path for %s: %s' % (entry['title'], e))
-                for fopt, dopt in self.options.items():
-                    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
-                modify_opts = {
-                    'queuetotop': entry.get('queuetotop', config.get('queuetotop')),
-                    'main_file_only': entry.get('main_file_only', config.get('main_file_only', False)),
-                    'main_file_ratio': entry.get('main_file_ratio', config.get('main_file_ratio')),
-                    'hide_sparse_files': entry.get('hide_sparse_files', config.get('hide_sparse_files', True)),
-                    'keep_subs': entry.get('keep_subs', config.get('keep_subs', True))
-                }
-                try:
-                    label = entry.render(entry.get('label', config['label']))
-                    modify_opts['label'] = format_label(label)
-                except RenderError as e:
-                    log.error('Error setting label for `%s`: %s', entry['title'], e)
-                try:
-                    movedone = entry.render(entry.get('movedone', config['movedone']))
-                    modify_opts['movedone'] = pathscrub(os.path.expanduser(movedone))
-                except RenderError as e:
-                    log.error('Error setting movedone for %s: %s' % (entry['title'], e))
-                try:
-                    content_filename = entry.get('content_filename', config.get('content_filename', ''))
-                    modify_opts['content_filename'] = pathscrub(entry.render(content_filename))
-                except RenderError as e:
-                    log.error('Error setting content_filename for %s: %s' % (entry['title'], e))
-
-                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)])
+                            log.warning('"%s" did not magnetize before the timeout elapsed, '
+                                        'file list unavailable for processing.', entry['title'])
                 else:
-                    dlist.append(add_entry(entry, add_opts).addCallbacks(
-                        set_torrent_options, on_fail, callbackArgs=(entry, modify_opts), errbackArgs=(task, entry)))
-            return defer.DeferredList(dlist)
-
-        dlist.append(client.core.get_session_state().addCallback(on_get_session_state))
-
-        def on_complete(result):
-            """Gets called when all of our tasks for deluge daemon are complete."""
-            client.disconnect()
-
-        tasks = defer.DeferredList(dlist).addBoth(on_complete)
-
-        def on_timeout(result):
-            """Gets called if tasks have not completed in 30 seconds.
-            Should only happen when something goes wrong."""
-            log.error('Timed out while adding torrents to deluge.')
-            log.debug('dlist: %s' % result.resultList)
-            client.disconnect()
-
-        # Schedule a disconnect to happen if FlexGet hangs while connected to Deluge
-        # Leave the timeout long, to give time for possible lookups to occur
-        reactor.callLater(600, lambda: tasks.called or on_timeout(tasks))
+                    try:
+                        added_torrent = self.client.call('core.add_torrent_file', entry['title'], filedump, add_opts)
+                    except Exception as e:
+                        log.info('%s was not added to deluge! %s', entry['title'], e)
+                        entry.fail('Could not be added to deluge')
+                self._set_torrent_options(added_torrent, entry, modify_opts)
 
     def on_task_learn(self, task, config):
         """ Make sure all temp files are cleaned up when entries are learned """
@@ -878,6 +476,157 @@ def on_task_abort(self, task, config):
         DelugePlugin.on_task_abort(self, task, config)
         self.on_task_learn(task, config)
 
+    def _format_label(self, label):
+        """Makes a string compliant with deluge label naming rules"""
+        return re.sub('[^\w-]+', '_', label.lower())
+
+    def _set_torrent_options(self, torrent_id, entry, opts):
+        """Gets called when a torrent was added to the daemon."""
+        log.info('%s successfully added to deluge.', entry['title'])
+        entry['deluge_id'] = torrent_id
+
+        if opts.get('movedone'):
+            self.client.call('core.set_torrent_move_completed', torrent_id, True)
+            self.client.call('core.set_torrent_move_completed_path', torrent_id, opts['movedone'])
+            log.debug('%s move on complete set to %s', entry['title'], opts['movedone'])
+        if opts.get('label'):
+            self.client.call('label.set_torrent', torrent_id, opts['label'])
+        if opts.get('queuetotop') is not None:
+            if opts['queuetotop']:
+                self.client.call('core.queue_top', [torrent_id])
+                log.debug('%s moved to top of queue', entry['title'])
+            else:
+                self.client.call('core.queue_bottom', [torrent_id])
+                log.debug('%s moved to bottom of queue', entry['title'])
+
+        status_keys = ['files', 'total_size', 'save_path', 'move_on_completed_path', 'move_on_completed', 'progress']
+        status = self.client.call('core.get_torrent_status', torrent_id, status_keys)
+        # Determine where the file should be
+        move_now_path = None
+        if opts.get('movedone'):
+            if status['progress'] == 100:
+                move_now_path = opts['movedone']
+            else:
+                # Deluge will unset the move completed option if we move the storage, forgo setting proper
+                # path, in favor of leaving proper final location.
+                log.debug('Not moving storage for %s, as this will prevent movedone.', entry['title'])
+        elif opts.get('path'):
+            move_now_path = opts['path']
+
+        if move_now_path and os.path.normpath(move_now_path) != os.path.normpath(status['save_path']):
+            log.debug('Moving storage for %s to %s', entry['title'], move_now_path)
+            self.client.call('core.move_storage', [torrent_id], move_now_path)
+
+        big_file_name = ''
+        if opts.get('content_filename') or opts.get('main_file_only'):
+            # find a file that makes up more than main_file_ratio (default: 90%) of the total size
+            main_file = None
+            for file in status['files']:
+                if file['size'] > (status['total_size'] * opts.get('main_file_ratio')):
+                    main_file = file
+                    break
+
+            def file_exists(filename):
+                # Checks the download path as well as the move completed path for existence of the file
+                if os.path.exists(os.path.join(status['save_path'], filename)):
+                    return True
+                elif status.get('move_on_completed') and status.get('move_on_completed_path'):
+                    if os.path.exists(os.path.join(status['move_on_completed_path'], filename)):
+                        return True
+                else:
+                    return False
+
+            def unused_name(name):
+                # If on local computer, tries appending a (#) suffix until a unique filename is found
+                if self.client.call('is_localhost'):
+                    counter = 2
+                    while file_exists(name):
+                        name = ''.join([os.path.splitext(name)[0],
+                                        " (", str(counter), ')',
+                                        os.path.splitext(name)[1]])
+                        counter += 1
+                else:
+                    log.debug('Cannot ensure content_filename is unique when adding to a remote deluge daemon.')
+                return name
+
+            def rename(file, new_name):
+                # Renames a file in torrent
+                self.client.call('core.rename_files', torrent_id, [(file['index'], new_name)])
+                log.debug('File %s in %s renamed to %s', file['path'], entry['title'], new_name)
+
+            if main_file is not None:
+                # proceed with renaming only if such a big file is found
+
+                # find the subtitle file
+                keep_subs = opts.get('keep_subs')
+                sub_file = None
+                if keep_subs:
+                    sub_exts = [".srt", ".sub"]
+                    for file in status['files']:
+                        ext = os.path.splitext(file['path'])[1]
+                        if ext in sub_exts:
+                            sub_file = file
+                            break
+
+                # check for single file torrents so we dont add unnecessary folders
+                top_files_dir = "/"
+                if os.path.dirname(main_file['path']) is not ("" or "/"):
+                    # check for top folder in user config
+                    if opts.get('content_filename') and os.path.dirname(opts['content_filename']) is not "":
+                        top_files_dir = os.path.dirname(opts['content_filename']) + "/"
+                    else:
+                        top_files_dir = os.path.dirname(main_file['path']) + "/"
+
+                if opts.get('content_filename'):
+                    # rename the main file
+                    big_file_name = (top_files_dir +
+                                     os.path.basename(opts['content_filename']) +
+                                     os.path.splitext(main_file['path'])[1])
+                    big_file_name = unused_name(big_file_name)
+                    rename(main_file, big_file_name)
+
+                    # rename subs along with the main file
+                    if sub_file is not None and keep_subs:
+                        sub_file_name = (os.path.splitext(big_file_name)[0] +
+                                         os.path.splitext(sub_file['path'])[1])
+                        rename(sub_file, sub_file_name)
+
+                if opts.get('main_file_only'):
+                    # download only the main file (and subs)
+                    file_priorities = [1 if f == main_file or f == sub_file and keep_subs else 0
+                                       for f in status['files']]
+                    self.client.call('core.set_torrent_file_priorities', torrent_id, file_priorities)
+
+                    if opts.get('hide_sparse_files'):
+                        # hide the other sparse files that are not supposed to download but are created anyway
+                        # http://dev.deluge-torrent.org/ticket/1827
+                        # Made sparse files behave better with deluge http://flexget.com/ticket/2881
+                        sparse_files = [f for f in status['files']
+                                        if f != main_file and (f != sub_file or not keep_subs)]
+                        rename_pairs = [(f['index'],
+                                         top_files_dir + ".sparse_files/" + os.path.basename(f['path']))
+                                        for f in sparse_files]
+                        self.client.call('core.rename_files', torrent_id, rename_pairs)
+            else:
+                log.warning('No files in "%s" are > %d%% of content size, no files renamed.', entry['title'],
+                            opts.get('main_file_ratio') * 100)
+
+        container_directory = pathscrub(entry.render(entry.get('container_directory',
+                                                               opts.get('container_directory', ''))))
+        if container_directory:
+            if big_file_name:
+                folder_structure = big_file_name.split(os.sep)
+            elif len(status['files']) > 0:
+                folder_structure = status['files'][0]['path'].split(os.sep)
+            else:
+                folder_structure = []
+            if len(folder_structure) > 1:
+                log.verbose('Renaming Folder %s to %s', folder_structure[0], container_directory)
+                self.client.call('core.rename_folder', torrent_id, folder_structure[0], container_directory)
+            else:
+                log.debug('container_directory specified however the torrent %s does not have a directory structure; '
+                          'skipping folder rename', entry['title'])
+
 
 @event('plugin.register')
 def register_plugin():


Test patch: