Exemple #1
0
 def __init__(self):
     super(WebApi, self).__init__("Web", depend=["SessionProxy"])
     self.host_list = ConfigManager("hostlist.conf.1.2", DEFAULT_HOSTS)
     self.core_config = CoreConfig()
     self.event_queue = EventQueue()
     try:
         self.sessionproxy = component.get("SessionProxy")
     except KeyError:
         self.sessionproxy = SessionProxy()
Exemple #2
0
 def __init__(self):
     super(WebApi, self).__init__('Web', depend=['SessionProxy'])
     self.hostlist = HostList()
     self.core_config = CoreConfig()
     self.event_queue = EventQueue()
     try:
         self.sessionproxy = component.get('SessionProxy')
     except KeyError:
         self.sessionproxy = SessionProxy()
Exemple #3
0
    def __init__(self, options, cmds, log_stream):
        component.Component.__init__(self, 'ConsoleUI')
        TermResizeHandler.__init__(self)
        self.options = options
        self.log_stream = log_stream

        # keep track of events for the log view
        self.events = []
        self.torrents = []
        self.statusbars = None
        self.modes = {}
        self.active_mode = None
        self.initialized = False

        try:
            locale.setlocale(locale.LC_ALL, '')
            self.encoding = locale.getpreferredencoding()
        except locale.Error:
            self.encoding = sys.getdefaultencoding()

        log.debug('Using encoding: %s', self.encoding)

        # start up the session proxy
        self.sessionproxy = SessionProxy()

        client.set_disconnect_callback(self.on_client_disconnect)

        # Set the interactive flag to indicate where we should print the output
        self.interactive = True
        self._commands = cmds
        self.coreconfig = CoreConfig()
Exemple #4
0
 def __init__(self):
     super(WebApi, self).__init__("Web", depend=["SessionProxy"])
     self.host_list = ConfigManager("hostlist.conf.1.2", DEFAULT_HOSTS)
     self.core_config = CoreConfig()
     self.event_queue = EventQueue()
     try:
         self.sessionproxy = component.get("SessionProxy")
     except KeyError:
         self.sessionproxy = SessionProxy()
Exemple #5
0
 def __init__(self):
     super(WebApi, self).__init__('Web', depend=['SessionProxy'])
     self.hostlist = HostList()
     self.core_config = CoreConfig()
     self.event_queue = EventQueue()
     try:
         self.sessionproxy = component.get('SessionProxy')
     except KeyError:
         self.sessionproxy = SessionProxy()
Exemple #6
0
    def __init__(self, args=None, cmds = None, daemon = None):
        component.Component.__init__(self, "ConsoleUI", 2)

        # keep track of events for the log view
        self.events = []

        try:
            locale.setlocale(locale.LC_ALL, '')
            self.encoding = locale.getpreferredencoding()
        except:
            self.encoding = sys.getdefaultencoding()

        log.debug("Using encoding: %s", self.encoding)


        # start up the session proxy
        self.sessionproxy = SessionProxy()

        client.set_disconnect_callback(self.on_client_disconnect)

        # Set the interactive flag to indicate where we should print the output
        self.interactive = True
        self._commands = cmds
        if args:
            args = args[0]
            self.interactive = False
            if not cmds:
                print "Sorry, couldn't find any commands"
                return
            else:
                from commander import Commander
                cmdr = Commander(cmds)
                if daemon:
                    cmdr.exec_args(args,*daemon)
                else:
                    cmdr.exec_args(args,None,None,None,None)
                

        self.coreconfig = CoreConfig()
        if self.interactive and not deluge.common.windows_check():
            # We use the curses.wrapper function to prevent the console from getting
            # messed up if an uncaught exception is experienced.
            import curses.wrapper
            curses.wrapper(self.run)
        elif self.interactive and deluge.common.windows_check():
            print """\nDeluge-console does not run in interactive mode on Windows. \n
Please use commands from the command line, eg:\n
    deluge-console.exe help
    deluge-console.exe info
    deluge-console.exe "add --help"
    deluge-console.exe "add -p c:\\mytorrents c:\\new.torrent"
            """
        else:
            reactor.run()
Exemple #7
0
class WebApi(JSONComponent):
    """
    The component that implements all the methods required for managing
    the web interface. The complete web json interface also exposes all the
    methods available from the core RPC.
    """

    def __init__(self):
        super(WebApi, self).__init__("Web", depend=["SessionProxy"])
        self.host_list = ConfigManager("hostlist.conf.1.2", DEFAULT_HOSTS)
        self.core_config = CoreConfig()
        self.event_queue = EventQueue()
        try:
            self.sessionproxy = component.get("SessionProxy")
        except KeyError:
            self.sessionproxy = SessionProxy()

    def get_host(self, host_id):
        """
        Return the information about a host

        :param host_id: the id of the host
        :type host_id: string
        :returns: the host information
        :rtype: list
        """
        for host in self.host_list["hosts"]:
            if host[0] == host_id:
                return host

    def start(self):
        self.core_config.start()
        self.sessionproxy.start()

    def stop(self):
        self.core_config.stop()
        self.sessionproxy.stop()

    @export
    def connect(self, host_id):
        """
        Connect the client to a daemon

        :param host_id: the id of the daemon in the host list
        :type host_id: string
        :returns: the methods the daemon supports
        :rtype: list
        """
        d = Deferred()
        def on_connected(methods):
            d.callback(methods)
        host = self.get_host(host_id)
        if host:
            self._json.connect(*host[1:]).addCallback(on_connected)

        return d

    @export
    def connected(self):
        """
        The current connection state.

        :returns: True if the client is connected
        :rtype: booleon
        """
        return client.connected()

    @export
    def disconnect(self):
        """
        Disconnect the web interface from the connected daemon.
        """
        client.disconnect()
        return True

    @export
    def update_ui(self, keys, filter_dict):
        """
        Gather the information required for updating the web interface.

        :param keys: the information about the torrents to gather
        :type keys: list
        :param filter_dict: the filters to apply when selecting torrents.
        :type filter_dict: dictionary
        :returns: The torrent and ui information.
        :rtype: dictionary
        """
        d = Deferred()
        ui_info = {
            "connected": client.connected(),
            "torrents": None,
            "filters": None,
            "stats": {
                "max_download": self.core_config.get("max_download_speed"),
                "max_upload": self.core_config.get("max_upload_speed"),
                "max_num_connections": self.core_config.get("max_connections_global")
            }
        }

        if not client.connected():
            d.callback(ui_info)
            return d

        def got_connections(connections):
            ui_info["stats"]["num_connections"] = connections

        def got_stats(stats):
            ui_info["stats"]["upload_rate"] = stats["payload_upload_rate"]
            ui_info["stats"]["download_rate"] = stats["payload_download_rate"]
            ui_info["stats"]["download_protocol_rate"] = stats["download_rate"] - stats["payload_download_rate"]
            ui_info["stats"]["upload_protocol_rate"] = stats["upload_rate"] - stats["payload_upload_rate"]
            ui_info["stats"]["dht_nodes"] = stats["dht_nodes"]
            ui_info["stats"]["has_incoming_connections"] = stats["has_incoming_connections"]

        def got_filters(filters):
            ui_info["filters"] = filters

        def got_free_space(free_space):
            ui_info["stats"]["free_space"] = free_space

        def got_torrents(torrents):
            ui_info["torrents"] = torrents

        def on_complete(result):
            d.callback(ui_info)

        d1 = component.get("SessionProxy").get_torrents_status(filter_dict, keys)
        d1.addCallback(got_torrents)

        d2 = client.core.get_filter_tree()
        d2.addCallback(got_filters)

        d3 = client.core.get_session_status([
            "payload_download_rate",
            "payload_upload_rate",
            "download_rate",
            "upload_rate",
            "dht_nodes",
            "has_incoming_connections"
        ])
        d3.addCallback(got_stats)

        d4 = client.core.get_num_connections()
        d4.addCallback(got_connections)

        d5 = client.core.get_free_space(self.core_config.get("download_location"))
        d5.addCallback(got_free_space)

        dl = DeferredList([d1, d2, d3, d4, d5], consumeErrors=True)
        dl.addCallback(on_complete)
        return d

    def _on_got_files(self, torrent, d):
        files = torrent.get("files")
        file_progress = torrent.get("file_progress")
        file_priorities = torrent.get("file_priorities")

        paths = []
        info = {}
        for index, torrent_file in enumerate(files):
            path = torrent_file["path"]
            paths.append(path)
            torrent_file["progress"] = file_progress[index]
            torrent_file["priority"] = file_priorities[index]
            torrent_file["index"] = index
            torrent_file["path"] = path
            info[path] = torrent_file

            # update the directory info
            dirname = os.path.dirname(path)
            while dirname:
                dirinfo = info.setdefault(dirname, {})
                dirinfo["size"] = dirinfo.get("size", 0) + torrent_file["size"]
                if "priority" not in dirinfo:
                    dirinfo["priority"] = torrent_file["priority"]
                else:
                    if dirinfo["priority"] != torrent_file["priority"]:
                        dirinfo["priority"] = 9

                progresses = dirinfo.setdefault("progresses", [])
                progresses.append(torrent_file["size"] * (torrent_file["progress"] / 100.0))
                dirinfo["progress"] = float(sum(progresses)) / dirinfo["size"] * 100
                dirinfo["path"] = dirname
                dirname = os.path.dirname(dirname)

        def walk(path, item):
            if item["type"] == "dir":
                item.update(info[path])
                return item
            else:
                item.update(info[path])
                return item

        file_tree = uicommon.FileTree2(paths)
        file_tree.walk(walk)
        d.callback(file_tree.get_tree())

    @export
    def get_torrent_status(self, torrent_id, keys):
        return component.get("SessionProxy").get_torrent_status(torrent_id, keys)

    @export
    def get_torrent_files(self, torrent_id):
        """
        Gets the files for a torrent in tree format

        :param torrent_id: the id of the torrent to retrieve.
        :type torrent_id: string
        :returns: The torrents files in a tree
        :rtype: dictionary
        """
        main_deferred = Deferred()
        d = component.get("SessionProxy").get_torrent_status(torrent_id, FILES_KEYS)
        d.addCallback(self._on_got_files, main_deferred)
        return main_deferred

    @export
    def download_torrent_from_url(self, url, cookie=None):
        """
        Download a torrent file from a url to a temporary directory.

        :param url: the url of the torrent
        :type url: string
        :returns: the temporary file name of the torrent file
        :rtype: string
        """

        def on_download_success(result):
            log.debug("Successfully downloaded %s to %s", url, result)
            return result

        def on_download_fail(result):
            if result.check(twisted.web.error.PageRedirect):
                new_url = urljoin(url, result.getErrorMessage().split(" to ")[1])
                result = httpdownloader.download_file(new_url, tmp_file, headers=headers)
                result.addCallbacks(on_download_success, on_download_fail)
            elif result.check(twisted.web.client.PartialDownloadError):
                result = httpdownloader.download_file(url, tmp_file, headers=headers,
                                                      allow_compression=False)
                result.addCallbacks(on_download_success, on_download_fail)
            else:
                log.error("Error occurred downloading torrent from %s", url)
                log.error("Reason: %s", result.getErrorMessage())
            return result

        tempdir = tempfile.mkdtemp(prefix="delugeweb-")
        tmp_file = os.path.join(tempdir, url.split("/")[-1])
        log.debug("filename: %s", tmp_file)
        headers = {}
        if cookie:
            headers["Cookie"] = cookie
            log.debug("cookie: %s", cookie)
        d = httpdownloader.download_file(url, tmp_file, headers=headers)
        d.addCallbacks(on_download_success, on_download_fail)
        return d

    @export
    def get_torrent_info(self, filename):
        """
        Return information about a torrent on the filesystem.

        :param filename: the path to the torrent
        :type filename: string

        :returns: information about the torrent:

        ::

            {
                "name": the torrent name,
                "files_tree": the files the torrent contains,
                "info_hash" the torrents info_hash
            }

        :rtype: dictionary
        """
        try:
            torrent_info = uicommon.TorrentInfo(filename.strip(), 2)
            return torrent_info.as_dict("name", "info_hash", "files_tree")
        except Exception, e:
            log.exception(e)
            return False
Exemple #8
0
class WebApi(JSONComponent):
    """
    The component that implements all the methods required for managing
    the web interface. The complete web json interface also exposes all the
    methods available from the core RPC.
    """

    XSS_VULN_KEYS = ['name', 'message', 'comment', 'tracker_status', 'peers']

    def __init__(self):
        super(WebApi, self).__init__('Web', depend=['SessionProxy'])
        self.hostlist = HostList()
        self.core_config = CoreConfig()
        self.event_queue = EventQueue()
        try:
            self.sessionproxy = component.get('SessionProxy')
        except KeyError:
            self.sessionproxy = SessionProxy()

    def disable(self):
        client.deregister_event_handler('PluginEnabledEvent',
                                        self._json.get_remote_methods)
        client.deregister_event_handler('PluginDisabledEvent',
                                        self._json.get_remote_methods)

        if client.is_standalone():
            component.get('Web.PluginManager').stop()
        else:
            client.disconnect()
            client.set_disconnect_callback(None)

    def enable(self):
        client.register_event_handler('PluginEnabledEvent',
                                      self._json.get_remote_methods)
        client.register_event_handler('PluginDisabledEvent',
                                      self._json.get_remote_methods)

        if client.is_standalone():
            component.get('Web.PluginManager').start()
        else:
            client.set_disconnect_callback(self._on_client_disconnect)
            default_host_id = component.get(
                'DelugeWeb').config['default_daemon']
            if default_host_id:
                return self.connect(default_host_id)

        return defer.succeed(True)

    def _on_client_connect(self, *args):
        """Handles client successfully connecting to the daemon.

        Invokes retrieving the method names and starts webapi and plugins.

        """
        d_methods = self._json.get_remote_methods()
        component.get('Web.PluginManager').start()
        self.start()
        return d_methods

    def _on_client_connect_fail(self, result, host_id):
        log.error(
            'Unable to connect to daemon, check host_id "%s" is correct.',
            host_id)

    def _on_client_disconnect(self, *args):
        component.get('Web.PluginManager').stop()
        return self.stop()

    def start(self):
        self.core_config.start()
        return self.sessionproxy.start()

    def stop(self):
        self.core_config.stop()
        self.sessionproxy.stop()
        return defer.succeed(True)

    @export
    def connect(self, host_id):
        """Connect the web client to a daemon.

        Args:
            host_id (str): The id of the daemon in the host list.

        Returns:
            Deferred: List of methods the daemon supports.
        """
        d = self.hostlist.connect_host(host_id)
        d.addCallback(self._on_client_connect)
        d.addErrback(self._on_client_connect_fail, host_id)
        return d

    @export
    def connected(self):
        """
        The current connection state.

        :returns: True if the client is connected
        :rtype: boolean
        """
        return client.connected()

    @export
    def disconnect(self):
        """
        Disconnect the web interface from the connected daemon.
        """
        d = client.disconnect()

        def on_disconnect(reason):
            return str(reason)

        d.addCallback(on_disconnect)
        return d

    @export
    def update_ui(self, keys, filter_dict):
        """
        Gather the information required for updating the web interface.

        :param keys: the information about the torrents to gather
        :type keys: list
        :param filter_dict: the filters to apply when selecting torrents.
        :type filter_dict: dictionary
        :returns: The torrent and UI information.
        :rtype: dictionary
        """
        d = Deferred()
        ui_info = {
            'connected': client.connected(),
            'torrents': None,
            'filters': None,
            'stats': {
                'max_download':
                self.core_config.get('max_download_speed'),
                'max_upload':
                self.core_config.get('max_upload_speed'),
                'max_num_connections':
                self.core_config.get('max_connections_global'),
            },
        }

        if not client.connected():
            d.callback(ui_info)
            return d

        def got_stats(stats):
            ui_info['stats']['num_connections'] = stats['num_peers']
            ui_info['stats']['upload_rate'] = stats['payload_upload_rate']
            ui_info['stats']['download_rate'] = stats['payload_download_rate']
            ui_info['stats']['download_protocol_rate'] = (
                stats['download_rate'] - stats['payload_download_rate'])
            ui_info['stats']['upload_protocol_rate'] = (
                stats['upload_rate'] - stats['payload_upload_rate'])
            ui_info['stats']['dht_nodes'] = stats['dht_nodes']
            ui_info['stats']['has_incoming_connections'] = stats[
                'has_incoming_connections']

        def got_filters(filters):
            ui_info['filters'] = filters

        def got_free_space(free_space):
            ui_info['stats']['free_space'] = free_space

        def got_external_ip(external_ip):
            ui_info['stats']['external_ip'] = external_ip

        def got_torrents(torrents):
            ui_info['torrents'] = torrents

        def on_complete(result):
            d.callback(ui_info)

        d1 = component.get('SessionProxy').get_torrents_status(
            filter_dict, keys)
        d1.addCallback(got_torrents)

        d2 = client.core.get_filter_tree()
        d2.addCallback(got_filters)

        d3 = client.core.get_session_status([
            'num_peers',
            'payload_download_rate',
            'payload_upload_rate',
            'download_rate',
            'upload_rate',
            'dht_nodes',
            'has_incoming_connections',
        ])
        d3.addCallback(got_stats)

        d4 = client.core.get_free_space(
            self.core_config.get('download_location'))
        d4.addCallback(got_free_space)

        d5 = client.core.get_external_ip()
        d5.addCallback(got_external_ip)

        dl = DeferredList([d1, d2, d3, d4, d5], consumeErrors=True)
        dl.addCallback(on_complete)
        return d

    def _on_got_files(self, torrent, d):
        files = torrent.get('files')
        file_progress = torrent.get('file_progress')
        file_priorities = torrent.get('file_priorities')

        paths = []
        info = {}
        for index, torrent_file in enumerate(files):
            path = xml_escape(torrent_file['path'])
            paths.append(path)
            torrent_file['progress'] = file_progress[index]
            torrent_file['priority'] = file_priorities[index]
            torrent_file['index'] = index
            torrent_file['path'] = path
            info[path] = torrent_file

            # update the directory info
            dirname = os.path.dirname(path)
            while dirname:
                dirinfo = info.setdefault(dirname, {})
                dirinfo['size'] = dirinfo.get('size', 0) + torrent_file['size']
                if 'priority' not in dirinfo:
                    dirinfo['priority'] = torrent_file['priority']
                else:
                    if dirinfo['priority'] != torrent_file['priority']:
                        dirinfo['priority'] = 9

                progresses = dirinfo.setdefault('progresses', [])
                progresses.append(torrent_file['size'] *
                                  torrent_file['progress'] / 100)
                dirinfo['progress'] = sum(progresses) / dirinfo['size'] * 100
                dirinfo['path'] = dirname
                dirname = os.path.dirname(dirname)

        def walk(path, item):
            if item['type'] == 'dir':
                item.update(info[path])
                return item
            else:
                item.update(info[path])
                return item

        file_tree = FileTree2(paths)
        file_tree.walk(walk)
        d.callback(file_tree.get_tree())

    def _on_torrent_status(self, torrent, d):
        for key in self.XSS_VULN_KEYS:
            try:
                if key == 'peers':
                    for peer in torrent[key]:
                        peer['client'] = xml_escape(peer['client'])
                else:
                    torrent[key] = xml_escape(torrent[key])
            except KeyError:
                pass
        d.callback(torrent)

    @export
    def get_torrent_status(self, torrent_id, keys):
        """Get the status for a torrent, filtered by status keys."""
        main_deferred = Deferred()
        d = component.get('SessionProxy').get_torrent_status(torrent_id, keys)
        d.addCallback(self._on_torrent_status, main_deferred)
        return main_deferred

    @export
    def get_torrent_files(self, torrent_id):
        """
        Gets the files for a torrent in tree format

        :param torrent_id: the id of the torrent to retrieve.
        :type torrent_id: string
        :returns: The torrents files in a tree
        :rtype: dictionary
        """
        main_deferred = Deferred()
        d = component.get('SessionProxy').get_torrent_status(
            torrent_id, FILES_KEYS)
        d.addCallback(self._on_got_files, main_deferred)
        return main_deferred

    @export
    def download_torrent_from_url(self, url, cookie=None):
        """
        Download a torrent file from a URL to a temporary directory.

        :param url: the URL of the torrent
        :type url: string
        :returns: the temporary file name of the torrent file
        :rtype: string
        """
        def on_download_success(result):
            log.debug('Successfully downloaded %s to %s', url, result)
            return result

        def on_download_fail(result):
            log.error('Failed to add torrent from url %s', url)
            return result

        tempdir = tempfile.mkdtemp(prefix='delugeweb-')
        tmp_file = os.path.join(tempdir, url.split('/')[-1])
        log.debug('filename: %s', tmp_file)
        headers = {}
        if cookie:
            headers['Cookie'] = cookie
            log.debug('cookie: %s', cookie)
        d = httpdownloader.download_file(url, tmp_file, headers=headers)
        d.addCallbacks(on_download_success, on_download_fail)
        return d

    @export
    def get_torrent_info(self, filename):
        """
        Return information about a torrent on the filesystem.

        :param filename: the path to the torrent
        :type filename: string

        :returns: information about the torrent:

        ::

            {
                "name": the torrent name,
                "files_tree": the files the torrent contains,
                "info_hash" the torrents info_hash
            }

        :rtype: dictionary
        """
        try:
            torrent_info = TorrentInfo(filename.strip(), 2)
            return torrent_info.as_dict('name', 'info_hash', 'files_tree')
        except Exception as ex:
            log.error(ex)
            return False

    @export
    def get_magnet_info(self, uri):
        """Parse a magnet URI for hash and name."""
        return get_magnet_info(uri)

    @export
    def add_torrents(self, torrents):
        """
        Add torrents by file

        :param torrents: A list of dictionaries containing the torrent \
            path and torrent options to add with.
        :type torrents: list

        ::

            json_api.web.add_torrents([{
                "path": "/tmp/deluge-web/some-torrent-file.torrent",
                "options": {"download_location": "/home/deluge/"}
            }])

        """
        deferreds = []

        for torrent in torrents:
            if is_magnet(torrent['path']):
                log.info(
                    'Adding torrent from magnet uri `%s` with options `%r`',
                    torrent['path'],
                    torrent['options'],
                )
                d = client.core.add_torrent_magnet(torrent['path'],
                                                   torrent['options'])
                deferreds.append(d)
            else:
                filename = os.path.basename(torrent['path'])
                with open(torrent['path'], 'rb') as _file:
                    fdump = b64encode(_file.read())
                log.info(
                    'Adding torrent from file `%s` with options `%r`',
                    filename,
                    torrent['options'],
                )
                d = client.core.add_torrent_file_async(filename, fdump,
                                                       torrent['options'])
                deferreds.append(d)
        return DeferredList(deferreds, consumeErrors=False)

    def _get_host(self, host_id):
        """Information about a host from supplied host id.

        Args:
            host_id (str): The id of the host.

        Returns:
            list: The host information, empty list if not found.

        """
        return list(self.hostlist.get_host_info(host_id))

    @export
    def get_hosts(self):
        """
        Return the hosts in the hostlist.
        """
        log.debug('get_hosts called')
        return self.hostlist.get_hosts_info()

    @export
    def get_host_status(self, host_id):
        """
        Returns the current status for the specified host.

        :param host_id: the hash id of the host
        :type host_id: string

        """
        def response(result):
            return result

        return self.hostlist.get_host_status(host_id).addCallback(response)

    @export
    def add_host(self, host, port, username='', password=''):
        """Adds a host to the list.

        Args:
            host (str): The IP or hostname of the deluge daemon.
            port (int): The port of the deluge daemon.
            username (str): The username to login to the daemon with.
            password (str): The password to login to the daemon with.

        Returns:
            tuple: A tuple of (bool, str). If True will contain the host_id, otherwise
                if False will contain the error message.
        """
        try:
            host_id = self.hostlist.add_host(host, port, username, password)
        except ValueError as ex:
            return False, str(ex)
        else:
            return True, host_id

    @export
    def edit_host(self, host_id, host, port, username='', password=''):
        """Edit host details in the hostlist.

        Args:
            host_id (str): The host identifying hash.
            host (str): The IP or hostname of the deluge daemon.
            port (int): The port of the deluge daemon.
            username (str): The username to login to the daemon with.
            password (str): The password to login to the daemon with.

        Returns:
            bool: True if successful, False otherwise.

        """
        return self.hostlist.update_host(host_id, host, port, username,
                                         password)

    @export
    def remove_host(self, host_id):
        """Removes a host from the hostlist.

        Args:
            host_id (str): The host identifying hash.

        Returns:
            bool: True if successful, False otherwise.

        """
        return self.hostlist.remove_host(host_id)

    @export
    def start_daemon(self, port):
        """
        Starts a local daemon.
        """
        client.start_daemon(port, get_config_dir())

    @export
    def stop_daemon(self, host_id):
        """
        Stops a running daemon.

        :param host_id: the hash id of the host
        :type host_id: string
        """
        main_deferred = Deferred()
        host = self._get_host(host_id)
        if not host:
            main_deferred.callback((False, _('Daemon does not exist')))
            return main_deferred

        try:

            def on_connect(connected, c):
                if not connected:
                    main_deferred.callback((False, _('Daemon not running')))
                    return
                c.daemon.shutdown()
                main_deferred.callback((True, ))

            def on_connect_failed(reason):
                main_deferred.callback((False, reason))

            host, port, user, password = host[1:5]
            c = Client()
            d = c.connect(host, port, user, password)
            d.addCallback(on_connect, c)
            d.addErrback(on_connect_failed)
        except Exception:
            main_deferred.callback((False, 'An error occurred'))
        return main_deferred

    @export
    def get_config(self):
        """
        Get the configuration dictionary for the web interface.

        :rtype: dictionary
        :returns: the configuration
        """
        config = component.get('DelugeWeb').config.config.copy()
        del config['sessions']
        del config['pwd_salt']
        del config['pwd_sha1']
        return config

    @export
    def set_config(self, config):
        """
        Sets the configuration dictionary for the web interface.

        :param config: The configuration options to update
        :type config: dictionary
        """
        web_config = component.get('DelugeWeb').config
        for key in config:
            if key in ['sessions', 'pwd_salt', 'pwd_sha1']:
                log.warning('Ignored attempt to overwrite web config key: %s',
                            key)
                continue
            web_config[key] = config[key]

    @export
    def get_plugins(self):
        """All available and enabled plugins within WebUI.

        Note:
            This does not represent all plugins from deluge.client.core.

        Returns:
            dict: A dict containing 'available_plugins' and 'enabled_plugins' lists.

        """

        return {
            'enabled_plugins':
            list(component.get('Web.PluginManager').plugins),
            'available_plugins':
            component.get('Web.PluginManager').available_plugins,
        }

    @export
    def get_plugin_info(self, name):
        """Get the details for a plugin."""
        return component.get('Web.PluginManager').get_plugin_info(name)

    @export
    def get_plugin_resources(self, name):
        """Get the resource data files for a plugin."""
        return component.get('Web.PluginManager').get_plugin_resources(name)

    @export
    def upload_plugin(self, filename, path):
        """Upload a plugin to config."""
        main_deferred = Deferred()

        shutil.copyfile(path,
                        os.path.join(get_config_dir(), 'plugins', filename))
        component.get('Web.PluginManager').scan_for_plugins()

        if client.is_localhost():
            client.core.rescan_plugins()
            return True
        with open(path, 'rb') as _file:
            plugin_data = b64encode(_file.read())

        def on_upload_complete(*args):
            client.core.rescan_plugins()
            component.get('Web.PluginManager').scan_for_plugins()
            main_deferred.callback(True)

        def on_upload_error(*args):
            main_deferred.callback(False)

        d = client.core.upload_plugin(filename, plugin_data)
        d.addCallback(on_upload_complete)
        d.addErrback(on_upload_error)
        return main_deferred

    @export
    def register_event_listener(self, event):
        """
        Add a listener to the event queue.

        :param event: The event name
        :type event: string
        """
        self.event_queue.add_listener(__request__.session_id, event)

    @export
    def deregister_event_listener(self, event):
        """
        Remove an event listener from the event queue.

        :param event: The event name
        :type event: string
        """
        self.event_queue.remove_listener(__request__.session_id, event)

    @export
    def get_events(self):
        """
        Retrieve the pending events for the session.
        """
        return self.event_queue.get_events(__request__.session_id)
Exemple #9
0
    def __init__(self, args=None):
        component.Component.__init__(self, "ConsoleUI", 2)

        self.batch_write = False

        try:
            locale.setlocale(locale.LC_ALL, '')
            self.encoding = locale.getpreferredencoding()
        except:
            self.encoding = sys.getdefaultencoding()

        log.debug("Using encoding: %s", self.encoding)
        # Load all the commands
        self._commands = load_commands(os.path.join(UI_PATH, 'commands'))

        client.set_disconnect_callback(self.on_client_disconnect)

        # Set the interactive flag to indicate where we should print the output
        self.interactive = True
        if args:
            # Multiple commands split by ";"
            commands = [arg.strip() for arg in ' '.join(args).split(';')]
            self.interactive = False

        # Try to connect to the daemon (localhost by default)
        def on_connect(result):
            def on_started(result):
                if not self.interactive:
                    def on_started(result):
                        def do_command(result, cmd):
                            return self.do_command(cmd)

                        d = defer.succeed(None)
                        # If we have commands, lets process them, then quit.
                        for command in commands:
                            d.addCallback(do_command, command)

                        if "quit" not in commands and "exit" not in commands:
                            d.addCallback(do_command, "quit")

                    # We need to wait for the rpcs in start() to finish before processing
                    # any of the commands.
                    self.started_deferred.addCallback(on_started)
            component.start().addCallback(on_started)

        def on_connect_fail(result):
            if not self.interactive:
                self.do_command('quit')

        connect_cmd = 'connect'
        if not self.interactive:
            if commands[0].startswith(connect_cmd):
                connect_cmd = commands.pop(0)
            elif 'help' in commands:
                self.do_command('help')
                return
        d = self.do_command(connect_cmd)
        d.addCallback(on_connect)
        d.addErrback(on_connect_fail)

        self.coreconfig = CoreConfig()
        if self.interactive and not deluge.common.windows_check():
            # We use the curses.wrapper function to prevent the console from getting
            # messed up if an uncaught exception is experienced.
            import curses.wrapper
            curses.wrapper(self.run)
        elif self.interactive and deluge.common.windows_check():
            print """\nDeluge-console does not run in interactive mode on Windows. \n
Please use commands from the command line, eg:\n
    deluge-console.exe help
    deluge-console.exe info
    deluge-console.exe "add --help"
    deluge-console.exe "add -p c:\\mytorrents c:\\new.torrent"
            """
        else:
            reactor.run()
Exemple #10
0
class WebApi(JSONComponent):
    """
    The component that implements all the methods required for managing
    the web interface. The complete web json interface also exposes all the
    methods available from the core RPC.
    """
    def __init__(self):
        super(WebApi, self).__init__("Web", depend=["SessionProxy"])
        self.host_list = ConfigManager("hostlist.conf.1.2", DEFAULT_HOSTS)
        self.core_config = CoreConfig()
        self.event_queue = EventQueue()
        try:
            self.sessionproxy = component.get("SessionProxy")
        except KeyError:
            self.sessionproxy = SessionProxy()

    def get_host(self, host_id):
        """
        Return the information about a host

        :param host_id: the id of the host
        :type host_id: string
        :returns: the host information
        :rtype: list
        """
        for host in self.host_list["hosts"]:
            if host[0] == host_id:
                return host

    def start(self):
        self.core_config.start()
        self.sessionproxy.start()

    def stop(self):
        self.core_config.stop()
        self.sessionproxy.stop()

    @export
    def connect(self, host_id):
        """
        Connect the client to a daemon

        :param host_id: the id of the daemon in the host list
        :type host_id: string
        :returns: the methods the daemon supports
        :rtype: list
        """
        d = Deferred()

        def on_connected(methods):
            d.callback(methods)

        host = self.get_host(host_id)
        if host:
            self._json.connect(*host[1:]).addCallback(on_connected)
        return d

    @export
    def connected(self):
        """
        The current connection state.

        :returns: True if the client is connected
        :rtype: booleon
        """
        return client.connected()

    @export
    def disconnect(self):
        """
        Disconnect the web interface from the connected daemon.
        """
        client.disconnect()
        return True

    @export
    def update_ui(self, keys, filter_dict):
        """
        Gather the information required for updating the web interface.

        :param keys: the information about the torrents to gather
        :type keys: list
        :param filter_dict: the filters to apply when selecting torrents.
        :type filter_dict: dictionary
        :returns: The torrent and ui information.
        :rtype: dictionary
        """
        d = Deferred()
        ui_info = {
            "connected": client.connected(),
            "torrents": None,
            "filters": None,
            "stats": {
                "max_download":
                self.core_config.get("max_download_speed"),
                "max_upload":
                self.core_config.get("max_upload_speed"),
                "max_num_connections":
                self.core_config.get("max_connections_global")
            }
        }

        if not client.connected():
            d.callback(ui_info)
            return d

        def got_connections(connections):
            ui_info["stats"]["num_connections"] = connections

        def got_stats(stats):
            ui_info["stats"]["upload_rate"] = stats["payload_upload_rate"]
            ui_info["stats"]["download_rate"] = stats["payload_download_rate"]
            ui_info["stats"]["download_protocol_rate"] = stats[
                "download_rate"] - stats["payload_download_rate"]
            ui_info["stats"]["upload_protocol_rate"] = stats[
                "upload_rate"] - stats["payload_upload_rate"]
            ui_info["stats"]["dht_nodes"] = stats["dht_nodes"]
            ui_info["stats"]["has_incoming_connections"] = stats[
                "has_incoming_connections"]

        def got_filters(filters):
            ui_info["filters"] = filters

        def got_free_space(free_space):
            ui_info["stats"]["free_space"] = free_space

        def got_torrents(torrents):
            ui_info["torrents"] = torrents

        def on_complete(result):
            d.callback(ui_info)

        d1 = component.get("SessionProxy").get_torrents_status(
            filter_dict, keys)
        d1.addCallback(got_torrents)

        d2 = client.core.get_filter_tree()
        d2.addCallback(got_filters)

        d3 = client.core.get_session_status([
            "payload_download_rate", "payload_upload_rate", "download_rate",
            "upload_rate", "dht_nodes", "has_incoming_connections"
        ])
        d3.addCallback(got_stats)

        d4 = client.core.get_num_connections()
        d4.addCallback(got_connections)

        d5 = client.core.get_free_space(
            self.core_config.get("download_location"))
        d5.addCallback(got_free_space)

        dl = DeferredList([d1, d2, d3, d4, d5], consumeErrors=True)
        dl.addCallback(on_complete)
        return d

    def _on_got_files(self, torrent, d):
        files = torrent.get("files")
        file_progress = torrent.get("file_progress")
        file_priorities = torrent.get("file_priorities")

        paths = []
        info = {}
        for index, torrent_file in enumerate(files):
            path = torrent_file["path"]
            paths.append(path)
            torrent_file["progress"] = file_progress[index]
            torrent_file["priority"] = file_priorities[index]
            torrent_file["index"] = index
            torrent_file["path"] = path
            info[path] = torrent_file

            # update the directory info
            dirname = os.path.dirname(path)
            while dirname:
                dirinfo = info.setdefault(dirname, {})
                dirinfo["size"] = dirinfo.get("size", 0) + torrent_file["size"]
                if "priority" not in dirinfo:
                    dirinfo["priority"] = torrent_file["priority"]
                else:
                    if dirinfo["priority"] != torrent_file["priority"]:
                        dirinfo["priority"] = 9

                progresses = dirinfo.setdefault("progresses", [])
                progresses.append(torrent_file["size"] *
                                  (torrent_file["progress"] / 100.0))
                dirinfo["progress"] = float(
                    sum(progresses)) / dirinfo["size"] * 100
                dirinfo["path"] = dirname
                dirname = os.path.dirname(dirname)

        def walk(path, item):
            if item["type"] == "dir":
                item.update(info[path])
                return item
            else:
                item.update(info[path])
                return item

        file_tree = uicommon.FileTree2(paths)
        file_tree.walk(walk)
        d.callback(file_tree.get_tree())

    @export
    def get_torrent_status(self, torrent_id, keys):
        return component.get("SessionProxy").get_torrent_status(
            torrent_id, keys)

    @export
    def get_torrent_files(self, torrent_id):
        """
        Gets the files for a torrent in tree format

        :param torrent_id: the id of the torrent to retrieve.
        :type torrent_id: string
        :returns: The torrents files in a tree
        :rtype: dictionary
        """
        main_deferred = Deferred()
        d = component.get("SessionProxy").get_torrent_status(
            torrent_id, FILES_KEYS)
        d.addCallback(self._on_got_files, main_deferred)
        return main_deferred

    @export
    def download_torrent_from_url(self, url, cookie=None):
        """
        Download a torrent file from a url to a temporary directory.

        :param url: the url of the torrent
        :type url: string
        :returns: the temporary file name of the torrent file
        :rtype: string
        """
        def on_download_success(result):
            log.debug("Successfully downloaded %s to %s", url, result)
            return result

        def on_download_fail(result):
            if result.check(twisted.web.error.PageRedirect):
                new_url = urljoin(url,
                                  result.getErrorMessage().split(" to ")[1])
                result = httpdownloader.download_file(new_url,
                                                      tmp_file,
                                                      headers=headers)
                result.addCallbacks(on_download_success, on_download_fail)
            elif result.check(twisted.web.client.PartialDownloadError):
                result = httpdownloader.download_file(url,
                                                      tmp_file,
                                                      headers=headers,
                                                      allow_compression=False)
                result.addCallbacks(on_download_success, on_download_fail)
            else:
                log.error("Error occured downloading torrent from %s", url)
                log.error("Reason: %s", result.getErrorMessage())
            return result

        tempdir = tempfile.mkdtemp(prefix="delugeweb-")
        tmp_file = os.path.join(tempdir, url.split("/")[-1])
        log.debug("filename: %s", tmp_file)
        headers = {}
        if cookie:
            headers["Cookie"] = cookie
            log.debug("cookie: %s", cookie)
        d = httpdownloader.download_file(url, tmp_file, headers=headers)
        d.addCallbacks(on_download_success, on_download_fail)
        return d

    @export
    def get_torrent_info(self, filename):
        """
        Return information about a torrent on the filesystem.

        :param filename: the path to the torrent
        :type filename: string

        :returns: information about the torrent:

        ::

            {
                "name": the torrent name,
                "files_tree": the files the torrent contains,
                "info_hash" the torrents info_hash
            }

        :rtype: dictionary
        """
        try:
            torrent_info = uicommon.TorrentInfo(filename.strip(), 2)
            return torrent_info.as_dict("name", "info_hash", "files_tree")
        except Exception, e:
            log.exception(e)
            return False
Exemple #11
0
class WebApi(JSONComponent):
    """
    The component that implements all the methods required for managing
    the web interface. The complete web json interface also exposes all the
    methods available from the core RPC.
    """

    def __init__(self):
        super(WebApi, self).__init__('Web', depend=['SessionProxy'])
        self.hostlist = HostList()
        self.core_config = CoreConfig()
        self.event_queue = EventQueue()
        try:
            self.sessionproxy = component.get('SessionProxy')
        except KeyError:
            self.sessionproxy = SessionProxy()

    def disable(self):
        client.deregister_event_handler('PluginEnabledEvent', self._json.get_remote_methods)
        client.deregister_event_handler('PluginDisabledEvent', self._json.get_remote_methods)

        if client.is_standalone():
            component.get('Web.PluginManager').stop()
        else:
            client.disconnect()
            client.set_disconnect_callback(None)

    def enable(self):
        client.register_event_handler('PluginEnabledEvent', self._json.get_remote_methods)
        client.register_event_handler('PluginDisabledEvent', self._json.get_remote_methods)

        if client.is_standalone():
            component.get('Web.PluginManager').start()
        else:
            client.set_disconnect_callback(self._on_client_disconnect)
            default_host_id = component.get('DelugeWeb').config['default_daemon']
            if default_host_id:
                return self.connect(default_host_id)

        return defer.succeed(True)

    def _on_client_connect(self, *args):
        """Handles client successfully connecting to the daemon.

        Invokes retrieving the method names and starts webapi and plugins.

        """
        d_methods = self._json.get_remote_methods()
        component.get('Web.PluginManager').start()
        self.start()
        return d_methods

    def _on_client_disconnect(self, *args):
        component.get('Web.PluginManager').stop()
        return self.stop()

    def start(self):
        self.core_config.start()
        return self.sessionproxy.start()

    def stop(self):
        self.core_config.stop()
        self.sessionproxy.stop()
        return defer.succeed(True)

    @export
    def connect(self, host_id):
        """Connect the web client to a daemon.

        Args:
            host_id (str): The id of the daemon in the host list.

        Returns:
            Deferred: List of methods the daemon supports.
        """
        return self.hostlist.connect_host(host_id).addCallback(self._on_client_connect)

    @export
    def connected(self):
        """
        The current connection state.

        :returns: True if the client is connected
        :rtype: booleon
        """
        return client.connected()

    @export
    def disconnect(self):
        """
        Disconnect the web interface from the connected daemon.
        """
        d = client.disconnect()

        def on_disconnect(reason):
            return str(reason)
        d.addCallback(on_disconnect)
        return d

    @export
    def update_ui(self, keys, filter_dict):
        """
        Gather the information required for updating the web interface.

        :param keys: the information about the torrents to gather
        :type keys: list
        :param filter_dict: the filters to apply when selecting torrents.
        :type filter_dict: dictionary
        :returns: The torrent and ui information.
        :rtype: dictionary
        """
        d = Deferred()
        ui_info = {
            'connected': client.connected(),
            'torrents': None,
            'filters': None,
            'stats': {
                'max_download': self.core_config.get('max_download_speed'),
                'max_upload': self.core_config.get('max_upload_speed'),
                'max_num_connections': self.core_config.get('max_connections_global')
            }
        }

        if not client.connected():
            d.callback(ui_info)
            return d

        def got_stats(stats):
            ui_info['stats']['num_connections'] = stats['num_peers']
            ui_info['stats']['upload_rate'] = stats['payload_upload_rate']
            ui_info['stats']['download_rate'] = stats['payload_download_rate']
            ui_info['stats']['download_protocol_rate'] = stats['download_rate'] - stats['payload_download_rate']
            ui_info['stats']['upload_protocol_rate'] = stats['upload_rate'] - stats['payload_upload_rate']
            ui_info['stats']['dht_nodes'] = stats['dht_nodes']
            ui_info['stats']['has_incoming_connections'] = stats['has_incoming_connections']

        def got_filters(filters):
            ui_info['filters'] = filters

        def got_free_space(free_space):
            ui_info['stats']['free_space'] = free_space

        def got_external_ip(external_ip):
            ui_info['stats']['external_ip'] = external_ip

        def got_torrents(torrents):
            ui_info['torrents'] = torrents

        def on_complete(result):
            d.callback(ui_info)

        d1 = component.get('SessionProxy').get_torrents_status(filter_dict, keys)
        d1.addCallback(got_torrents)

        d2 = client.core.get_filter_tree()
        d2.addCallback(got_filters)

        d3 = client.core.get_session_status([
            'num_peers',
            'payload_download_rate',
            'payload_upload_rate',
            'download_rate',
            'upload_rate',
            'dht_nodes',
            'has_incoming_connections'
        ])
        d3.addCallback(got_stats)

        d4 = client.core.get_free_space(self.core_config.get('download_location'))
        d4.addCallback(got_free_space)

        d5 = client.core.get_external_ip()
        d5.addCallback(got_external_ip)

        dl = DeferredList([d1, d2, d3, d4, d5], consumeErrors=True)
        dl.addCallback(on_complete)
        return d

    def _on_got_files(self, torrent, d):
        files = torrent.get('files')
        file_progress = torrent.get('file_progress')
        file_priorities = torrent.get('file_priorities')

        paths = []
        info = {}
        for index, torrent_file in enumerate(files):
            path = torrent_file['path']
            paths.append(path)
            torrent_file['progress'] = file_progress[index]
            torrent_file['priority'] = file_priorities[index]
            torrent_file['index'] = index
            torrent_file['path'] = path
            info[path] = torrent_file

            # update the directory info
            dirname = os.path.dirname(path)
            while dirname:
                dirinfo = info.setdefault(dirname, {})
                dirinfo['size'] = dirinfo.get('size', 0) + torrent_file['size']
                if 'priority' not in dirinfo:
                    dirinfo['priority'] = torrent_file['priority']
                else:
                    if dirinfo['priority'] != torrent_file['priority']:
                        dirinfo['priority'] = 9

                progresses = dirinfo.setdefault('progresses', [])
                progresses.append(torrent_file['size'] * torrent_file['progress'] / 100)
                dirinfo['progress'] = sum(progresses) / dirinfo['size'] * 100
                dirinfo['path'] = dirname
                dirname = os.path.dirname(dirname)

        def walk(path, item):
            if item['type'] == 'dir':
                item.update(info[path])
                return item
            else:
                item.update(info[path])
                return item

        file_tree = FileTree2(paths)
        file_tree.walk(walk)
        d.callback(file_tree.get_tree())

    @export
    def get_torrent_status(self, torrent_id, keys):
        return component.get('SessionProxy').get_torrent_status(torrent_id, keys)

    @export
    def get_torrent_files(self, torrent_id):
        """
        Gets the files for a torrent in tree format

        :param torrent_id: the id of the torrent to retrieve.
        :type torrent_id: string
        :returns: The torrents files in a tree
        :rtype: dictionary
        """
        main_deferred = Deferred()
        d = component.get('SessionProxy').get_torrent_status(torrent_id, FILES_KEYS)
        d.addCallback(self._on_got_files, main_deferred)
        return main_deferred

    @export
    def download_torrent_from_url(self, url, cookie=None):
        """
        Download a torrent file from a url to a temporary directory.

        :param url: the url of the torrent
        :type url: string
        :returns: the temporary file name of the torrent file
        :rtype: string
        """

        def on_download_success(result):
            log.debug('Successfully downloaded %s to %s', url, result)
            return result

        def on_download_fail(result):
            log.error('Failed to add torrent from url %s', url)
            return result

        tempdir = tempfile.mkdtemp(prefix='delugeweb-')
        tmp_file = os.path.join(tempdir, url.split('/')[-1])
        log.debug('filename: %s', tmp_file)
        headers = {}
        if cookie:
            headers['Cookie'] = cookie
            log.debug('cookie: %s', cookie)
        d = httpdownloader.download_file(url, tmp_file, headers=headers)
        d.addCallbacks(on_download_success, on_download_fail)
        return d

    @export
    def get_torrent_info(self, filename):
        """
        Return information about a torrent on the filesystem.

        :param filename: the path to the torrent
        :type filename: string

        :returns: information about the torrent:

        ::

            {
                "name": the torrent name,
                "files_tree": the files the torrent contains,
                "info_hash" the torrents info_hash
            }

        :rtype: dictionary
        """
        try:
            torrent_info = TorrentInfo(filename.strip(), 2)
            return torrent_info.as_dict('name', 'info_hash', 'files_tree')
        except Exception as ex:
            log.error(ex)
            return False

    @export
    def get_magnet_info(self, uri):
        return get_magnet_info(uri)

    @export
    def add_torrents(self, torrents):
        """
        Add torrents by file

        :param torrents: A list of dictionaries containing the torrent \
            path and torrent options to add with.
        :type torrents: list

        ::

            json_api.web.add_torrents([{
                "path": "/tmp/deluge-web/some-torrent-file.torrent",
                "options": {"download_location": "/home/deluge/"}
            }])

        """
        deferreds = []

        for torrent in torrents:
            if is_magnet(torrent['path']):
                log.info('Adding torrent from magnet uri `%s` with options `%r`',
                         torrent['path'], torrent['options'])
                d = client.core.add_torrent_magnet(torrent['path'], torrent['options'])
                deferreds.append(d)
            else:
                filename = os.path.basename(torrent['path'])
                with open(torrent['path'], 'rb') as _file:
                    fdump = base64.encodestring(_file.read())
                log.info('Adding torrent from file `%s` with options `%r`',
                         filename, torrent['options'])
                d = client.core.add_torrent_file(filename, fdump, torrent['options'])
                deferreds.append(d)
        return DeferredList(deferreds, consumeErrors=False)

    def _get_host(self, host_id):
        """Information about a host from supplied host id.

        Args:
            host_id (str): The id of the host.

        Returns:
            list: The host information, empty list if not found.

        """
        return list(self.hostlist.get_host_info(host_id))

    @export
    def get_hosts(self):
        """
        Return the hosts in the hostlist.
        """
        log.debug('get_hosts called')
        return self.hostlist.get_hosts_info()

    @export
    def get_host_status(self, host_id):
        """
        Returns the current status for the specified host.

        :param host_id: the hash id of the host
        :type host_id: string

        """
        def response(result):
            return result

        return self.hostlist.get_host_status(host_id).addCallback(response)

    @export
    def add_host(self, host, port, username='', password=''):
        """Adds a host to the list.

        Args:
            host (str): The IP or hostname of the deluge daemon.
            port (int): The port of the deluge daemon.
            username (str): The username to login to the daemon with.
            password (str): The password to login to the daemon with.

        Returns:
            tuple: A tuple of (bool, str). If True will contain the host_id, otherwise
                if False will contain the error message.
        """
        try:
            host_id = self.hostlist.add_host(host, port, username, password)
        except ValueError as ex:
            return False, str(ex)
        else:
            return True, host_id

    @export
    def edit_host(self, host_id, host, port, username='', password=''):
        """Edit host details in the hostlist.

        Args:
            host_id (str): The host identifying hash.
            host (str): The IP or hostname of the deluge daemon.
            port (int): The port of the deluge daemon.
            username (str): The username to login to the daemon with.
            password (str): The password to login to the daemon with.

        Returns:
            bool: True if succesful, False otherwise.

        """
        return self.hostlist.update_host(host_id, host, port, username, password)

    @export
    def remove_host(self, host_id):
        """Removes a host from the hostlist.

        Args:
            host_id (str): The host identifying hash.

        Returns:
            bool: True if succesful, False otherwise.

        """
        return self.hostlist.remove_host(host_id)

    @export
    def start_daemon(self, port):
        """
        Starts a local daemon.
        """
        client.start_daemon(port, get_config_dir())

    @export
    def stop_daemon(self, host_id):
        """
        Stops a running daemon.

        :param host_id: the hash id of the host
        :type host_id: string
        """
        main_deferred = Deferred()
        host = self._get_host(host_id)
        if not host:
            main_deferred.callback((False, _('Daemon does not exist')))
            return main_deferred

        try:
            def on_connect(connected, c):
                if not connected:
                    main_deferred.callback((False, _('Daemon not running')))
                    return
                c.daemon.shutdown()
                main_deferred.callback((True, ))

            def on_connect_failed(reason):
                main_deferred.callback((False, reason))

            host, port, user, password = host[1:5]
            c = Client()
            d = c.connect(host, port, user, password)
            d.addCallback(on_connect, c)
            d.addErrback(on_connect_failed)
        except Exception:
            main_deferred.callback((False, 'An error occurred'))
        return main_deferred

    @export
    def get_config(self):
        """
        Get the configuration dictionary for the web interface.

        :rtype: dictionary
        :returns: the configuration
        """
        config = component.get('DelugeWeb').config.config.copy()
        del config['sessions']
        del config['pwd_salt']
        del config['pwd_sha1']
        return config

    @export
    def set_config(self, config):
        """
        Sets the configuration dictionary for the web interface.

        :param config: The configuration options to update
        :type config: dictionary
        """
        web_config = component.get('DelugeWeb').config
        for key in config:
            if key in ['sessions', 'pwd_salt', 'pwd_sha1']:
                log.warn('Ignored attempt to overwrite web config key: %s', key)
                continue
            web_config[key] = config[key]

    @export
    def get_plugins(self):
        """All available and enabled plugins within WebUI.

        Note:
            This does not represent all plugins from deluge.client.core.

        Returns:
            dict: A dict containing 'available_plugins' and 'enabled_plugins' lists.

        """

        return {
            'enabled_plugins': list(component.get('Web.PluginManager').plugins),
            'available_plugins': component.get('Web.PluginManager').available_plugins
        }

    @export
    def get_plugin_info(self, name):
        return component.get('Web.PluginManager').get_plugin_info(name)

    @export
    def get_plugin_resources(self, name):
        return component.get('Web.PluginManager').get_plugin_resources(name)

    @export
    def upload_plugin(self, filename, path):
        main_deferred = Deferred()

        shutil.copyfile(path, os.path.join(get_config_dir(), 'plugins', filename))
        component.get('Web.PluginManager').scan_for_plugins()

        if client.is_localhost():
            client.core.rescan_plugins()
            return True
        with open(path, 'rb') as _file:
            plugin_data = base64.encodestring(_file.read())

        def on_upload_complete(*args):
            client.core.rescan_plugins()
            component.get('Web.PluginManager').scan_for_plugins()
            main_deferred.callback(True)

        def on_upload_error(*args):
            main_deferred.callback(False)

        d = client.core.upload_plugin(filename, plugin_data)
        d.addCallback(on_upload_complete)
        d.addErrback(on_upload_error)
        return main_deferred

    @export
    def register_event_listener(self, event):
        """
        Add a listener to the event queue.

        :param event: The event name
        :type event: string
        """
        self.event_queue.add_listener(__request__.session_id, event)

    @export
    def deregister_event_listener(self, event):
        """
        Remove an event listener from the event queue.

        :param event: The event name
        :type event: string
        """
        self.event_queue.remove_listener(__request__.session_id, event)

    @export
    def get_events(self):
        """
        Retrieve the pending events for the session.
        """
        return self.event_queue.get_events(__request__.session_id)