Пример #1
0
class AutoAdd(component.Component):
    def __init__(self):
        component.Component.__init__(self, "AutoAdd", depend=["TorrentManager"], interval=5)
        # Get the core config
        self.config = ConfigManager("core.conf")

        # A list of filenames
        self.invalid_torrents = []
        # Filename:Attempts
        self.attempts = {}

        # Register set functions
        self.config.register_set_function("autoadd_enable",
            self._on_autoadd_enable, apply_now=True)
        self.config.register_set_function("autoadd_location",
            self._on_autoadd_location)

    def update(self):
        if not self.config["autoadd_enable"]:
            # We shouldn't be updating because autoadd is not enabled
            component.pause("AutoAdd")
            return

        # Check the auto add folder for new torrents to add
        if not os.path.isdir(self.config["autoadd_location"]):
            log.warning("Invalid AutoAdd folder: %s", self.config["autoadd_location"])
            component.pause("AutoAdd")
            return

        for filename in os.listdir(self.config["autoadd_location"]):
            try:
                filepath = os.path.join(self.config["autoadd_location"], filename)
            except UnicodeDecodeError, e:
                log.error("Unable to auto add torrent due to improper filename encoding: %s", e)
                continue
            if os.path.isfile(filepath) and filename.endswith(".torrent"):
                try:
                    filedump = self.load_torrent(filepath)
                except (RuntimeError, Exception), e:
                    # If the torrent is invalid, we keep track of it so that we
                    # can try again on the next pass.  This is because some
                    # torrents may not be fully saved during the pass.
                    log.debug("Torrent is invalid: %s", e)
                    if filename in self.invalid_torrents:
                        self.attempts[filename] += 1
                        if self.attempts[filename] >= MAX_NUM_ATTEMPTS:
                            os.rename(filepath, filepath + ".invalid")
                            del self.attempts[filename]
                            self.invalid_torrents.remove(filename)
                    else:
                        self.invalid_torrents.append(filename)
                        self.attempts[filename] = 1
                    continue

                # The torrent looks good, so lets add it to the session
                component.get("TorrentManager").add(filedump=filedump, filename=filename)

                os.remove(filepath)
Пример #2
0
class MainWindow(component.Component):
    def __init__(self):
        if wnck:
            self.screen = wnck.screen_get_default()
        component.Component.__init__(self, 'MainWindow', interval=2)
        self.config = ConfigManager('gtkui.conf')
        self.main_builder = gtk.Builder()

        # Patch this GtkBuilder to avoid connecting signals from elsewhere
        #
        # Think about splitting up  mainwindow gtkbuilder file into the necessary parts
        # to avoid GtkBuilder monkey patch. Those parts would then need adding to mainwindow 'by hand'.
        self.gtk_builder_signals_holder = _GtkBuilderSignalsHolder()
        self.main_builder.prev_connect_signals = copy.deepcopy(self.main_builder.connect_signals)

        def patched_connect_signals(*a, **k):
            raise RuntimeError('In order to connect signals to this GtkBuilder instance please use '
                               '"component.get(\'MainWindow\').connect_signals()"')
        self.main_builder.connect_signals = patched_connect_signals

        # Get Gtk Builder files Main Window, New release dialog, and Tabs.
        for filename in ('main_window.ui', 'main_window.new_release.ui', 'main_window.tabs.ui',
                         'main_window.tabs.menu_file.ui', 'main_window.tabs.menu_peer.ui'):
            self.main_builder.add_from_file(
                resource_filename('deluge.ui.gtkui', os.path.join('glade', filename)))

        self.window = self.main_builder.get_object('main_window')
        self.window.set_icon(deluge.ui.gtkui.common.get_deluge_icon())
        self.vpaned = self.main_builder.get_object('vpaned')
        self.initial_vpaned_position = self.config['window_pane_position']

        # Keep a list of components to pause and resume when changing window state.
        self.child_components = ['TorrentView', 'StatusBar', 'TorrentDetails']

        # Load the window state
        self.load_window_state()

        # Keep track of window minimization state so we don't update UI when it is minimized.
        self.is_minimized = False
        self.restart = False

        self.window.drag_dest_set(gtk.DEST_DEFAULT_ALL, [('text/uri-list', 0, 80)], ACTION_COPY)

        # Connect events
        self.window.connect('window-state-event', self.on_window_state_event)
        self.window.connect('configure-event', self.on_window_configure_event)
        self.window.connect('delete-event', self.on_window_delete_event)
        self.window.connect('drag-data-received', self.on_drag_data_received_event)
        self.vpaned.connect('notify::position', self.on_vpaned_position_event)
        self.window.connect('expose-event', self.on_expose_event)

        self.config.register_set_function('show_rate_in_title', self._on_set_show_rate_in_title, apply_now=False)

        client.register_event_handler('NewVersionAvailableEvent', self.on_newversionavailable_event)

    def connect_signals(self, mapping_or_class):
        self.gtk_builder_signals_holder.connect_signals(mapping_or_class)

    def first_show(self):
        self.main_builder.prev_connect_signals(self.gtk_builder_signals_holder)
        self.vpaned.set_position(self.initial_vpaned_position)
        if not (
                self.config['start_in_tray'] and self.config['enable_system_tray']
        ) and not self.window.get_property('visible'):
            log.debug('Showing window')
            self.show()

        while gtk.events_pending():
            gtk.main_iteration()

    def show(self):
        component.resume(self.child_components)
        self.window.show()

    def hide(self):
        component.get('TorrentView').save_state()
        component.pause(self.child_components)

        # Store the x, y positions for when we restore the window
        self.config['window_x_pos'], self.config['window_y_pos'] = self.window.get_position()
        self.window.hide()

    def present(self):
        def restore():
            # Restore the proper x,y coords for the window prior to showing it
            component.resume(self.child_components)
            self.window.present()
            self.load_window_state()

        if self.config['lock_tray'] and not self.visible():
            dialog = PasswordDialog(_('Enter your password to show Deluge...'))

            def on_dialog_response(response_id):
                if response_id == gtk.RESPONSE_OK:
                    if self.config['tray_password'] == sha(dialog.get_password()).hexdigest():
                        restore()
            dialog.run().addCallback(on_dialog_response)
        else:
            restore()

    def active(self):
        """Returns True if the window is active, False if not."""
        return self.window.is_active()

    def visible(self):
        """Returns True if window is visible, False if not."""
        return self.window.get_property('visible')

    def get_builder(self):
        """Returns a reference to the main window GTK builder object."""
        return self.main_builder

    def quit(self, shutdown=False, restart=False):
        """Quits the GtkUI application.

        Args:
            shutdown (bool): Whether or not to shutdown the daemon as well.
            restart (bool): Whether or not to restart the application after closing.

        """

        def quit_gtkui():
            def stop_gtk_reactor(result=None):
                self.restart = restart
                try:
                    reactor.callLater(0, reactor.fireSystemEvent, 'gtkui_close')
                except ReactorNotRunning:
                    log.debug('Attempted to stop the reactor but it is not running...')

            if shutdown:
                client.daemon.shutdown().addCallback(stop_gtk_reactor)
            elif not client.is_standalone() and client.connected():
                client.disconnect().addCallback(stop_gtk_reactor)
            else:
                stop_gtk_reactor()

        if self.config['lock_tray'] and not self.visible():
            dialog = PasswordDialog(_('Enter your password to Quit Deluge...'))

            def on_dialog_response(response_id):
                if response_id == gtk.RESPONSE_OK:
                    if self.config['tray_password'] == sha(dialog.get_password()).hexdigest():
                        quit_gtkui()
            dialog.run().addCallback(on_dialog_response)
        else:
            quit_gtkui()

    def load_window_state(self):
        if self.config['window_x_pos'] == -32000 or self.config['window_x_pos'] == -32000:
            self.config['window_x_pos'] = self.config['window_y_pos'] = 0

        self.window.move(self.config['window_x_pos'], self.config['window_y_pos'])
        self.window.resize(self.config['window_width'], self.config['window_height'])
        if self.config['window_maximized']:
            self.window.maximize()

    def on_window_configure_event(self, widget, event):
        if not self.config['window_maximized'] and self.visible:
            self.config['window_x_pos'], self.config['window_y_pos'] = self.window.get_position()
            self.config['window_width'] = event.width
            self.config['window_height'] = event.height

    def on_window_state_event(self, widget, event):
        if event.changed_mask & WINDOW_STATE_MAXIMIZED:
            if event.new_window_state & WINDOW_STATE_MAXIMIZED:
                log.debug('pos: %s', self.window.get_position())
                self.config['window_maximized'] = True
            elif not event.new_window_state & WINDOW_STATE_WITHDRAWN:
                self.config['window_maximized'] = False
        if event.changed_mask & WINDOW_STATE_ICONIFIED:
            if event.new_window_state & WINDOW_STATE_ICONIFIED:
                log.debug('MainWindow is minimized..')
                component.get('TorrentView').save_state()
                component.pause(self.child_components)
                self.is_minimized = True
            else:
                log.debug('MainWindow is not minimized..')
                component.resume(self.child_components)
                self.is_minimized = False
        return False

    def on_window_delete_event(self, widget, event):
        if self.config['close_to_tray'] and self.config['enable_system_tray']:
            self.hide()
        else:
            self.quit()

        return True

    def on_vpaned_position_event(self, obj, param):
        self.config['window_pane_position'] = self.vpaned.get_position()

    def on_drag_data_received_event(self, widget, drag_context, x, y, selection_data, info, timestamp):
        log.debug('Selection(s) dropped on main window %s', selection_data.get_text())
        if selection_data.get_uris():
            process_args(selection_data.get_uris())
        else:
            process_args(selection_data.get_text().split())
        drag_context.finish(True, True, timestamp)

    def on_expose_event(self, widget, event):
        component.get('SystemTray').blink(False)

    def stop(self):
        self.window.set_title('Deluge')

    def update(self):
        # Update the window title
        def _on_get_session_status(status):
            download_rate = fspeed(status['payload_download_rate'], precision=0, shortform=True)
            upload_rate = fspeed(status['payload_upload_rate'], precision=0, shortform=True)
            self.window.set_title(_('D: %s U: %s - Deluge' % (download_rate, upload_rate)))
        if self.config['show_rate_in_title']:
            client.core.get_session_status(
                ['payload_download_rate', 'payload_upload_rate']
                ).addCallback(_on_get_session_status)

    def _on_set_show_rate_in_title(self, key, value):
        if value:
            self.update()
        else:
            self.window.set_title(_('Deluge'))

    def on_newversionavailable_event(self, new_version):
        if self.config['show_new_releases']:
            from deluge.ui.gtkui.new_release_dialog import NewReleaseDialog
            reactor.callLater(5.0, NewReleaseDialog().show, new_version)

    def is_on_active_workspace(self):
        """Determines if MainWindow is on the active workspace.

        Returns:
            bool: True if on active workspace (or wnck module not available), otherwise False.

        """
        if wnck:
            self.screen.force_update()
            win = wnck.window_get(self.window.get_window().xid)
            if win:
                active_wksp = win.get_screen().get_active_workspace()
                if active_wksp:
                    return win.is_on_workspace(active_wksp)
                return False
        return True
Пример #3
0
class SystemTray(component.Component):
    def __init__(self):
        component.Component.__init__(self, 'SystemTray', interval=4)
        self.mainwindow = component.get('MainWindow')
        self.config = ConfigManager('gtk3ui.conf')
        # List of widgets that need to be hidden when not connected to a host
        self.hide_widget_list = [
            'menuitem_add_torrent',
            'menuitem_pause_session',
            'menuitem_resume_session',
            'menuitem_download_limit',
            'menuitem_upload_limit',
            'menuitem_quitdaemon',
            'separatormenuitem1',
            'separatormenuitem2',
            'separatormenuitem3',
            'separatormenuitem4',
        ]
        self.config.register_set_function('enable_system_tray',
                                          self.on_enable_system_tray_set)
        # bit of a hack to prevent function from doing something on startup
        self.__enabled_set_once = False
        self.config.register_set_function('enable_appindicator',
                                          self.on_enable_appindicator_set)

        self.max_download_speed = -1.0
        self.download_rate = 0.0
        self.max_upload_speed = -1.0
        self.upload_rate = 0.0

        self.config_value_changed_dict = {
            'max_download_speed': self._on_max_download_speed,
            'max_upload_speed': self._on_max_upload_speed,
        }

    def enable(self):
        """Enables the system tray icon."""
        self.builder = Builder()
        self.builder.add_from_file(
            resource_filename(__package__,
                              os.path.join('glade', 'tray_menu.ui')))

        self.builder.connect_signals(self)

        self.tray_menu = self.builder.get_object('tray_menu')

        if AppIndicator3 and self.config['enable_appindicator']:
            log.debug('Enabling the Application Indicator...')
            self.indicator = AppIndicator3.Indicator.new(
                'deluge',
                'deluge-panel',
                AppIndicator3.IndicatorCategory.APPLICATION_STATUS,
            )
            self.indicator.set_property('title', _('Deluge'))

            # Pass the menu to the Application Indicator
            self.indicator.set_menu(self.tray_menu)

            # Make sure the status of the Show Window MenuItem is correct
            self._sig_win_hide = self.mainwindow.window.connect(
                'hide', self._on_window_hide)
            self._sig_win_show = self.mainwindow.window.connect(
                'show', self._on_window_show)
            if self.mainwindow.visible():
                self.builder.get_object('menuitem_show_deluge').set_active(
                    True)
            else:
                self.builder.get_object('menuitem_show_deluge').set_active(
                    False)

            # Show the Application Indicator
            self.indicator.set_status(AppIndicator3.IndicatorStatus.ACTIVE)

        else:
            log.debug('Enabling the system tray icon..')
            if windows_check() or osx_check():
                self.tray = StatusIcon.new_from_pixbuf(get_logo(32))
            else:
                self.tray = StatusIcon.new_from_icon_name('deluge-panel')

            self.tray.connect('activate', self.on_tray_clicked)
            self.tray.connect('popup-menu', self.on_tray_popup)

        self.builder.get_object('download-limit-image').set_from_file(
            get_pixmap('downloading16.png'))
        self.builder.get_object('upload-limit-image').set_from_file(
            get_pixmap('seeding16.png'))

        client.register_event_handler('ConfigValueChangedEvent',
                                      self.config_value_changed)
        if client.connected():
            # We're connected so we need to get some values from the core
            self.__start()
        else:
            # Hide menu widgets because we're not connected to a host.
            for widget in self.hide_widget_list:
                self.builder.get_object(widget).hide()

    def __start(self):
        if self.config['enable_system_tray']:

            if self.config['standalone']:
                try:
                    self.hide_widget_list.remove('menuitem_quitdaemon')
                    self.hide_widget_list.remove('separatormenuitem4')
                except ValueError:
                    pass
                self.builder.get_object('menuitem_quitdaemon').hide()
                self.builder.get_object('separatormenuitem4').hide()

            # Show widgets in the hide list because we've connected to a host
            for widget in self.hide_widget_list:
                self.builder.get_object(widget).show()

            # Build the bandwidth speed limit menus
            self.build_tray_bwsetsubmenu()

            # Get some config values
            def update_config_values(configs):
                self._on_max_download_speed(configs['max_download_speed'])
                self._on_max_upload_speed(configs['max_upload_speed'])

            client.core.get_config_values(
                ['max_download_speed',
                 'max_upload_speed']).addCallback(update_config_values)

    def start(self):
        self.__start()

    def stop(self):
        if self.config['enable_system_tray'] and not self.config[
                'enable_appindicator']:
            try:
                # Hide widgets in hide list because we're not connected to a host
                for widget in self.hide_widget_list:
                    self.builder.get_object(widget).hide()
            except Exception as ex:
                log.debug('Unable to hide system tray menu widgets: %s', ex)

            self.tray.set_tooltip_text(
                _('Deluge') + '\n' + _('Not Connected...'))

    def shutdown(self):
        if self.config['enable_system_tray']:
            if AppIndicator3 and self.config['enable_appindicator']:
                self.indicator.set_status(
                    AppIndicator3.IndicatorStatus.PASSIVE)
            else:
                self.tray.set_visible(False)

    def send_status_request(self):
        client.core.get_session_status(
            ['payload_upload_rate',
             'payload_download_rate']).addCallback(self._on_get_session_status)

    def config_value_changed(self, key, value):
        """This is called when we received a config_value_changed signal from
        the core."""
        if key in self.config_value_changed_dict:
            self.config_value_changed_dict[key](value)

    def _on_max_download_speed(self, max_download_speed):
        if self.max_download_speed != max_download_speed:
            self.max_download_speed = max_download_speed
            self.build_tray_bwsetsubmenu()

    def _on_max_upload_speed(self, max_upload_speed):
        if self.max_upload_speed != max_upload_speed:
            self.max_upload_speed = max_upload_speed
            self.build_tray_bwsetsubmenu()

    def _on_get_session_status(self, status):
        self.download_rate = fspeed(status['payload_download_rate'],
                                    shortform=True)
        self.upload_rate = fspeed(status['payload_upload_rate'],
                                  shortform=True)

    def update(self):
        if not self.config['enable_system_tray']:
            return

        # Tool tip text not available for appindicator
        if AppIndicator3 and self.config['enable_appindicator']:
            if self.mainwindow.visible():
                self.builder.get_object('menuitem_show_deluge').set_active(
                    True)
            else:
                self.builder.get_object('menuitem_show_deluge').set_active(
                    False)
            return

        # Set the tool tip text
        max_download_speed = self.max_download_speed
        max_upload_speed = self.max_upload_speed

        if max_download_speed == -1:
            max_download_speed = _('Unlimited')
        else:
            max_download_speed = '%s %s' % (max_download_speed, _('K/s'))
        if max_upload_speed == -1:
            max_upload_speed = _('Unlimited')
        else:
            max_upload_speed = '%s %s' % (max_upload_speed, _('K/s'))

        msg = '%s\n%s: %s (%s)\n%s: %s (%s)' % (
            _('Deluge'),
            _('Down'),
            self.download_rate,
            max_download_speed,
            _('Up'),
            self.upload_rate,
            max_upload_speed,
        )

        # Set the tooltip
        self.tray.set_tooltip_text(msg)

        self.send_status_request()

    def build_tray_bwsetsubmenu(self):
        # Create the Download speed list sub-menu
        submenu_bwdownset = build_menu_radio_list(
            self.config['tray_download_speed_list'],
            self.on_tray_setbwdown,
            self.max_download_speed,
            _('K/s'),
            show_notset=True,
            show_other=True,
        )

        # Create the Upload speed list sub-menu
        submenu_bwupset = build_menu_radio_list(
            self.config['tray_upload_speed_list'],
            self.on_tray_setbwup,
            self.max_upload_speed,
            _('K/s'),
            show_notset=True,
            show_other=True,
        )
        # Add the sub-menus to the tray menu
        self.builder.get_object('menuitem_download_limit').set_submenu(
            submenu_bwdownset)
        self.builder.get_object('menuitem_upload_limit').set_submenu(
            submenu_bwupset)

        # Show the sub-menus for all to see
        submenu_bwdownset.show_all()
        submenu_bwupset.show_all()

    def disable(self, invert_app_ind_conf=False):
        """Disables the system tray icon or Appindicator."""
        try:
            if invert_app_ind_conf:
                app_ind_conf = not self.config['enable_appindicator']
            else:
                app_ind_conf = self.config['enable_appindicator']
            if AppIndicator3 and app_ind_conf:
                if hasattr(self, '_sig_win_hide'):
                    self.mainwindow.window.disconnect(self._sig_win_hide)
                    self.mainwindow.window.disconnect(self._sig_win_show)
                    log.debug('Disabling the application indicator..')

                self.indicator.set_status(
                    AppIndicator3.IndicatorStatus.PASSIVE)
                del self.indicator
            else:
                log.debug('Disabling the system tray icon..')
                self.tray.set_visible(False)
                del self.tray
            del self.builder
            del self.tray_menu
        except Exception as ex:
            log.debug('Unable to disable system tray: %s', ex)

    def blink(self, value):
        try:
            self.tray.set_blinking(value)
        except AttributeError:
            # If self.tray is not defined then ignore. This happens when the
            # tray icon is not being used.
            pass

    def on_enable_system_tray_set(self, key, value):
        """Called whenever the 'enable_system_tray' config key is modified"""
        if value:
            self.enable()
        else:
            self.disable()

    def on_enable_appindicator_set(self, key, value):
        """Called whenever the 'enable_appindicator' config key is modified"""
        if self.__enabled_set_once:
            self.disable(True)
            self.enable()
        self.__enabled_set_once = True

    def on_tray_clicked(self, icon):
        """Called when the tray icon is left clicked."""
        self.blink(False)

        if self.mainwindow.active():
            self.mainwindow.hide()
        else:
            self.mainwindow.present()

    def on_tray_popup(self, status_icon, button, activate_time):
        """Called when the tray icon is right clicked."""
        self.blink(False)

        if self.mainwindow.visible():
            self.builder.get_object('menuitem_show_deluge').set_active(True)
        else:
            self.builder.get_object('menuitem_show_deluge').set_active(False)

        popup_function = StatusIcon.position_menu
        if windows_check() or osx_check():
            popup_function = None
            button = 0
        self.tray_menu.popup(None, None, popup_function, status_icon, button,
                             activate_time)

    def on_menuitem_show_deluge_activate(self, menuitem):
        log.debug('on_menuitem_show_deluge_activate')
        if menuitem.get_active() and not self.mainwindow.visible():
            self.mainwindow.present()
        elif not menuitem.get_active() and self.mainwindow.visible():
            self.mainwindow.hide()

    def on_menuitem_add_torrent_activate(self, menuitem):
        log.debug('on_menuitem_add_torrent_activate')
        component.get('AddTorrentDialog').show()

    def on_menuitem_pause_session_activate(self, menuitem):
        log.debug('on_menuitem_pause_session_activate')
        client.core.pause_session()

    def on_menuitem_resume_session_activate(self, menuitem):
        log.debug('on_menuitem_resume_session_activate')
        client.core.resume_session()

    def on_menuitem_quit_activate(self, menuitem):
        log.debug('on_menuitem_quit_activate')
        self.mainwindow.quit()

    def on_menuitem_quitdaemon_activate(self, menuitem):
        log.debug('on_menuitem_quitdaemon_activate')
        self.mainwindow.quit(shutdown=True)

    def on_tray_setbwdown(self, widget, data=None):
        if isinstance(widget, RadioMenuItem):
            # ignore previous radiomenuitem value
            if not widget.get_active():
                return
        self.setbwlimit(
            widget,
            _('Download Speed Limit'),
            _('Set the maximum download speed'),
            'max_download_speed',
            'tray_download_speed_list',
            self.max_download_speed,
            'downloading.svg',
        )

    def on_tray_setbwup(self, widget, data=None):
        if isinstance(widget, RadioMenuItem):
            # ignore previous radiomenuitem value
            if not widget.get_active():
                return
        self.setbwlimit(
            widget,
            _('Upload Speed Limit'),
            _('Set the maximum upload speed'),
            'max_upload_speed',
            'tray_upload_speed_list',
            self.max_upload_speed,
            'seeding.svg',
        )

    def _on_window_hide(self, widget, data=None):
        """_on_window_hide - update the menuitem's status"""
        log.debug('_on_window_hide')
        self.builder.get_object('menuitem_show_deluge').set_active(False)

    def _on_window_show(self, widget, data=None):
        """_on_window_show - update the menuitem's status"""
        log.debug('_on_window_show')
        self.builder.get_object('menuitem_show_deluge').set_active(True)

    def setbwlimit(self, widget, header, text, core_key, ui_key, default,
                   image):
        """Sets the bandwidth limit based on the user selection."""
        def set_value(value):
            log.debug('setbwlimit: %s', value)
            if value is None:
                return
            elif value == 0:
                value = -1
            # Set the config in the core
            client.core.set_config({core_key: value})

        if widget.get_name() == 'unlimited':
            set_value(-1)
        elif widget.get_name() == 'other':
            dialog = OtherDialog(header, text, _('K/s'), image, default)
            dialog.run().addCallback(set_value)
        else:
            set_value(widget.get_children()[0].get_text().split(' ')[0])
Пример #4
0
class MainWindow(component.Component):
    def __init__(self):
        component.Component.__init__(self, "MainWindow", interval=2)
        self.config = ConfigManager("gtkui.conf")
        # Get the glade file for the main window
        self.main_glade = gtk.glade.XML(
            deluge.common.resource_filename(
                "deluge.ui.gtkui", os.path.join("glade", "main_window.glade")))

        self.window = self.main_glade.get_widget("main_window")

        self.window.set_icon(common.get_deluge_icon())

        self.vpaned = self.main_glade.get_widget("vpaned")
        self.initial_vpaned_position = self.config["window_pane_position"]

        # Load the window state
        self.load_window_state()

        # Keep track of window's minimization state so that we don't update the
        # UI when it is minimized.
        self.is_minimized = False

        self.window.drag_dest_set(gtk.DEST_DEFAULT_ALL,
                                  [('text/uri-list', 0, 80)],
                                  gtk.gdk.ACTION_COPY)

        # Connect events
        self.window.connect("window-state-event", self.on_window_state_event)
        self.window.connect("configure-event", self.on_window_configure_event)
        self.window.connect("delete-event", self.on_window_delete_event)
        self.window.connect("drag-data-received",
                            self.on_drag_data_received_event)
        self.vpaned.connect("notify::position", self.on_vpaned_position_event)
        self.window.connect("expose-event", self.on_expose_event)

        self.config.register_set_function("show_rate_in_title",
                                          self._on_set_show_rate_in_title,
                                          apply_now=False)

        client.register_event_handler("NewVersionAvailableEvent",
                                      self.on_newversionavailable_event)
        client.register_event_handler("TorrentFinishedEvent",
                                      self.on_torrentfinished_event)

    def first_show(self):
        if not(self.config["start_in_tray"] and \
               self.config["enable_system_tray"]) and not \
                self.window.get_property("visible"):
            log.debug("Showing window")
            self.show()
            while gtk.events_pending():
                gtk.main_iteration(False)
            self.vpaned.set_position(self.initial_vpaned_position)

    def show(self):
        try:
            component.resume("TorrentView")
            component.resume("StatusBar")
            component.resume("TorrentDetails")
        except:
            pass

        self.window.show()

    def hide(self):
        component.pause("TorrentView")
        component.get("TorrentView").save_state()
        component.pause("StatusBar")
        component.pause("TorrentDetails")
        # Store the x, y positions for when we restore the window
        self.window_x_pos = self.window.get_position()[0]
        self.window_y_pos = self.window.get_position()[1]
        self.window.hide()

    def present(self):
        # Restore the proper x,y coords for the window prior to showing it
        try:
            self.config["window_x_pos"] = self.window_x_pos
            self.config["window_y_pos"] = self.window_y_pos
        except:
            pass
        try:
            component.resume("TorrentView")
            component.resume("StatusBar")
            component.resume("TorrentDetails")
        except:
            pass

        self.window.present()
        self.load_window_state()

    def active(self):
        """Returns True if the window is active, False if not."""
        return self.window.is_active()

    def visible(self):
        """Returns True if window is visible, False if not."""
        return self.window.get_property("visible")

    def get_glade(self):
        """Returns a reference to the main window glade object."""
        return self.main_glade

    def quit(self, shutdown=False):
        """
        Quits the GtkUI

        :param shutdown: whether or not to shutdown the daemon as well
        :type shutdown: boolean
        """
        if shutdown:

            def on_daemon_shutdown(result):
                reactor.stop()

            client.daemon.shutdown().addCallback(on_daemon_shutdown)
            return
        if client.is_classicmode():
            reactor.stop()
            return
        if not client.connected():
            reactor.stop()
            return

        def on_client_disconnected(result):
            reactor.stop()

        client.disconnect().addCallback(on_client_disconnected)

    def load_window_state(self):
        x = self.config["window_x_pos"]
        y = self.config["window_y_pos"]
        w = self.config["window_width"]
        h = self.config["window_height"]
        self.window.move(x, y)
        self.window.resize(w, h)
        if self.config["window_maximized"]:
            self.window.maximize()

    def on_window_configure_event(self, widget, event):
        if not self.config["window_maximized"] and self.visible:
            self.config["window_x_pos"] = self.window.get_position()[0]
            self.config["window_y_pos"] = self.window.get_position()[1]
            self.config["window_width"] = event.width
            self.config["window_height"] = event.height

    def on_window_state_event(self, widget, event):
        if event.changed_mask & gtk.gdk.WINDOW_STATE_MAXIMIZED:
            if event.new_window_state & gtk.gdk.WINDOW_STATE_MAXIMIZED:
                log.debug("pos: %s", self.window.get_position())
                self.config["window_maximized"] = True
            else:
                self.config["window_maximized"] = False
        if event.changed_mask & gtk.gdk.WINDOW_STATE_ICONIFIED:
            if event.new_window_state & gtk.gdk.WINDOW_STATE_ICONIFIED:
                log.debug("MainWindow is minimized..")
                component.pause("TorrentView")
                component.pause("StatusBar")
                self.is_minimized = True
            else:
                log.debug("MainWindow is not minimized..")
                try:
                    component.resume("TorrentView")
                    component.resume("StatusBar")
                except:
                    pass
                self.is_minimized = False
        return False

    def on_window_delete_event(self, widget, event):
        if self.config["close_to_tray"] and self.config["enable_system_tray"]:
            self.hide()
        else:
            self.quit()

        return True

    def on_vpaned_position_event(self, obj, param):
        self.config["window_pane_position"] = self.vpaned.get_position()

    def on_drag_data_received_event(self, widget, drag_context, x, y,
                                    selection_data, info, timestamp):
        args = []
        for uri in selection_data.data.split():
            if deluge.common.windows_check():
                args.append(urllib.url2pathname(uri[7:]))
            else:
                args.append(urllib.unquote(urlparse(uri).path))
        process_args(args)
        drag_context.finish(True, True)

    def on_expose_event(self, widget, event):
        component.get("SystemTray").blink(False)

    def stop(self):
        self.window.set_title("Deluge")

    def update(self):
        # Update the window title
        def _on_get_session_status(status):
            download_rate = deluge.common.fspeed(status["download_rate"])
            upload_rate = deluge.common.fspeed(status["upload_rate"])
            self.window.set_title(
                "Deluge - %s %s %s %s" %
                (_("Down:"), download_rate, _("Up:"), upload_rate))

        if self.config["show_rate_in_title"]:
            client.core.get_session_status(
                ["download_rate",
                 "upload_rate"]).addCallback(_on_get_session_status)

    def _on_set_show_rate_in_title(self, key, value):
        if value:
            self.update()
        else:
            self.window.set_title("Deluge")

    def on_newversionavailable_event(self, new_version):
        if self.config["show_new_releases"]:
            from deluge.ui.gtkui.new_release_dialog import NewReleaseDialog
            reactor.callLater(5.0, NewReleaseDialog().show, new_version)

    def on_torrentfinished_event(self, torrent_id):
        from deluge.ui.gtkui.notification import Notification
        Notification().notify(torrent_id)
Пример #5
0
class SystemTray(component.Component):
    def __init__(self):
        component.Component.__init__(self, "SystemTray", interval=4)
        self.window = component.get("MainWindow")
        self.config = ConfigManager("gtkui.conf")
        # List of widgets that need to be hidden when not connected to a host
        self.hide_widget_list = [
            "menuitem_add_torrent", "menuitem_pause_all",
            "menuitem_resume_all", "menuitem_download_limit",
            "menuitem_upload_limit", "menuitem_quitdaemon",
            "separatormenuitem1", "separatormenuitem2", "separatormenuitem3",
            "separatormenuitem4"
        ]
        self.config.register_set_function("enable_system_tray",
                                          self.on_enable_system_tray_set)
        # bit of a hack to prevent function from doing something on startup
        self.__enabled_set_once = False
        self.config.register_set_function("enable_appindicator",
                                          self.on_enable_appindicator_set)

        self.max_download_speed = -1.0
        self.download_rate = 0.0
        self.max_upload_speed = -1.0
        self.upload_rate = 0.0

        self.config_value_changed_dict = {
            "max_download_speed": self._on_max_download_speed,
            "max_upload_speed": self._on_max_upload_speed
        }

    def enable(self):
        """Enables the system tray icon."""
        self.builder = gtk.Builder()
        self.builder.add_from_file(
            deluge.common.resource_filename(
                "deluge.ui.gtkui", os.path.join("glade", "tray_menu.ui")))

        self.builder.connect_signals({
            "on_menuitem_show_deluge_activate":
            self.on_menuitem_show_deluge_activate,
            "on_menuitem_add_torrent_activate":
            self.on_menuitem_add_torrent_activate,
            "on_menuitem_pause_all_activate":
            self.on_menuitem_pause_all_activate,
            "on_menuitem_resume_all_activate":
            self.on_menuitem_resume_all_activate,
            "on_menuitem_quit_activate":
            self.on_menuitem_quit_activate,
            "on_menuitem_quitdaemon_activate":
            self.on_menuitem_quitdaemon_activate
        })

        self.tray_menu = self.builder.get_object("tray_menu")

        if appindicator and self.config["enable_appindicator"]:
            log.debug("Enabling the Application Indicator..")
            self.indicator = appindicator.Indicator(
                "deluge", "deluge", appindicator.CATEGORY_APPLICATION_STATUS)
            try:
                self.indicator.set_property("title", _("Deluge"))
            except TypeError:
                # Catch 'title' property error for previous appindicator versions
                pass
            # Pass the menu to the Application Indicator
            self.indicator.set_menu(self.tray_menu)

            # Make sure the status of the Show Window MenuItem is correct
            self._sig_win_hide = self.window.window.connect(
                "hide", self._on_window_hide)
            self._sig_win_show = self.window.window.connect(
                "show", self._on_window_show)
            if self.window.visible():
                self.builder.get_object("menuitem_show_deluge").set_active(
                    True)
            else:
                self.builder.get_object("menuitem_show_deluge").set_active(
                    False)

            # Show the Application Indicator
            self.indicator.set_status(appindicator.STATUS_ACTIVE)

        else:
            log.debug("Enabling the system tray icon..")
            if deluge.common.windows_check() or deluge.common.osx_check():
                self.tray = gtk.status_icon_new_from_pixbuf(
                    common.get_logo(32))
            else:
                try:
                    self.tray = gtk.status_icon_new_from_icon_name("deluge")
                except:
                    log.warning(
                        "Update PyGTK to 2.10 or greater for SystemTray..")
                    return

            self.tray.connect("activate", self.on_tray_clicked)
            self.tray.connect("popup-menu", self.on_tray_popup)

        self.builder.get_object("download-limit-image").set_from_file(
            deluge.common.get_pixmap("downloading16.png"))
        self.builder.get_object("upload-limit-image").set_from_file(
            deluge.common.get_pixmap("seeding16.png"))

        client.register_event_handler("ConfigValueChangedEvent",
                                      self.config_value_changed)
        if client.connected():
            # We're connected so we need to get some values from the core
            self.__start()
        else:
            # Hide menu widgets because we're not connected to a host.
            for widget in self.hide_widget_list:
                self.builder.get_object(widget).hide()

    def __start(self):
        if self.config["enable_system_tray"]:

            if self.config["classic_mode"]:
                try:
                    self.hide_widget_list.remove("menuitem_quitdaemon")
                    self.hide_widget_list.remove("separatormenuitem4")
                except ValueError:
                    pass
                self.builder.get_object("menuitem_quitdaemon").hide()
                self.builder.get_object("separatormenuitem4").hide()

            # Show widgets in the hide list because we've connected to a host
            for widget in self.hide_widget_list:
                self.builder.get_object(widget).show()

            # Build the bandwidth speed limit menus
            self.build_tray_bwsetsubmenu()

            # Get some config values
            def update_config_values(configs):
                self._on_max_download_speed(configs["max_download_speed"])
                self._on_max_upload_speed(configs["max_upload_speed"])

            client.core.get_config_values(
                ["max_download_speed",
                 "max_upload_speed"]).addCallback(update_config_values)

    def start(self):
        self.__start()

    def stop(self):
        if self.config["enable_system_tray"] and not self.config[
                "enable_appindicator"]:
            try:
                # Hide widgets in hide list because we're not connected to a host
                for widget in self.hide_widget_list:
                    self.builder.get_object(widget).hide()
            except Exception, e:
                log.debug("Unable to hide system tray menu widgets: %s", e)

            self.tray.set_tooltip(_("Deluge") + "\n" + _("Not Connected..."))
Пример #6
0
class AutoAdd(component.Component):
    def __init__(self):
        component.Component.__init__(self,
                                     "AutoAdd",
                                     depend=["TorrentManager"],
                                     interval=5)
        # Get the core config
        self.config = ConfigManager("core.conf")

        # A list of filenames
        self.invalid_torrents = []
        # Filename:Attempts
        self.attempts = {}

        # Register set functions
        self.config.register_set_function("autoadd_enable",
                                          self._on_autoadd_enable,
                                          apply_now=True)
        self.config.register_set_function("autoadd_location",
                                          self._on_autoadd_location)

    def update(self):
        if not self.config["autoadd_enable"]:
            # We shouldn't be updating because autoadd is not enabled
            component.pause("AutoAdd")
            return

        # Check the auto add folder for new torrents to add
        if not os.path.isdir(self.config["autoadd_location"]):
            log.warning("Invalid AutoAdd folder: %s",
                        self.config["autoadd_location"])
            component.pause("AutoAdd")
            return

        for filename in os.listdir(self.config["autoadd_location"]):
            try:
                filepath = os.path.join(self.config["autoadd_location"],
                                        filename)
            except UnicodeDecodeError, e:
                log.error(
                    "Unable to auto add torrent due to improper filename encoding: %s",
                    e)
                continue
            if os.path.isfile(filepath) and filename.endswith(".torrent"):
                try:
                    filedump = self.load_torrent(filepath)
                except (RuntimeError, Exception), e:
                    # If the torrent is invalid, we keep track of it so that we
                    # can try again on the next pass.  This is because some
                    # torrents may not be fully saved during the pass.
                    log.debug("Torrent is invalid: %s", e)
                    if filename in self.invalid_torrents:
                        self.attempts[filename] += 1
                        if self.attempts[filename] >= MAX_NUM_ATTEMPTS:
                            os.rename(filepath, filepath + ".invalid")
                            del self.attempts[filename]
                            self.invalid_torrents.remove(filename)
                    else:
                        self.invalid_torrents.append(filename)
                        self.attempts[filename] = 1
                    continue

                # The torrent looks good, so lets add it to the session
                component.get("TorrentManager").add(filedump=filedump,
                                                    filename=filename)

                os.remove(filepath)
Пример #7
0
class MainWindow(component.Component):
    def __init__(self):
        if wnck:
            self.screen = wnck.screen_get_default()
        component.Component.__init__(self, "MainWindow", interval=2)
        self.config = ConfigManager("gtkui.conf")
        # Get the glade file for the main window
        self.main_glade = gtk.glade.XML(
                    pkg_resources.resource_filename("deluge.ui.gtkui",
                                                    "glade/main_window.glade"))

        self.window = self.main_glade.get_widget("main_window")

        self.window.set_icon(common.get_deluge_icon())

        self.vpaned = self.main_glade.get_widget("vpaned")
        self.initial_vpaned_position = self.config["window_pane_position"]

        # Load the window state
        self.load_window_state()

        # Keep track of window's minimization state so that we don't update the
        # UI when it is minimized.
        self.is_minimized = False

        self.window.drag_dest_set(gtk.DEST_DEFAULT_ALL, [('text/uri-list', 0,
            80)], gtk.gdk.ACTION_COPY)

        # Connect events
        self.window.connect("window-state-event", self.on_window_state_event)
        self.window.connect("configure-event", self.on_window_configure_event)
        self.window.connect("delete-event", self.on_window_delete_event)
        self.window.connect("drag-data-received", self.on_drag_data_received_event)
        self.vpaned.connect("notify::position", self.on_vpaned_position_event)
        self.window.connect("expose-event", self.on_expose_event)

        self.config.register_set_function("show_rate_in_title", self._on_set_show_rate_in_title, apply_now=False)

        client.register_event_handler("NewVersionAvailableEvent", self.on_newversionavailable_event)
        client.register_event_handler("TorrentFinishedEvent", self.on_torrentfinished_event)

    def first_show(self):
        if not(self.config["start_in_tray"] and \
               self.config["enable_system_tray"]) and not \
                self.window.get_property("visible"):
            log.debug("Showing window")
            self.show()
            while gtk.events_pending():
                gtk.main_iteration(False)
            self.vpaned.set_position(self.initial_vpaned_position)

    def show(self):
        try:
            component.resume("TorrentView")
            component.resume("StatusBar")
            component.resume("TorrentDetails")
        except:
            pass
        self.window.show()

    def hide(self):
        component.pause("TorrentView")
        component.get("TorrentView").save_state()
        component.pause("StatusBar")
        component.pause("TorrentDetails")
        # Store the x, y positions for when we restore the window
        self.window_x_pos = self.window.get_position()[0]
        self.window_y_pos = self.window.get_position()[1]
        self.window.hide()

    def present(self):
        def restore():
            # Restore the proper x,y coords for the window prior to showing it
            try:
                if self.window_x_pos == -32000 or self.window_y_pos == -32000:
                    self.config["window_x_pos"] = 0
                    self.config["window_y_pos"] = 0
                else:
                    self.config["window_x_pos"] = self.window_x_pos
                    self.config["window_y_pos"] = self.window_y_pos
            except:
                pass
            try:
                component.resume("TorrentView")
                component.resume("StatusBar")
                component.resume("TorrentDetails")
            except:
                pass

            self.window.present()
            self.load_window_state()

        if self.config["lock_tray"] and not self.visible():
            dialog = PasswordDialog(_("Enter your password to show Deluge..."))
            def on_dialog_response(response_id):
                if response_id == gtk.RESPONSE_OK:
                    if self.config["tray_password"] == sha(dialog.get_password()).hexdigest():
                        restore()
            dialog.run().addCallback(on_dialog_response)
        else:
            restore()

    def active(self):
        """Returns True if the window is active, False if not."""
        return self.window.is_active()

    def visible(self):
        """Returns True if window is visible, False if not."""
        return self.window.get_property("visible")

    def get_glade(self):
        """Returns a reference to the main window glade object."""
        return self.main_glade

    def quit(self, shutdown=False):
        """
        Quits the GtkUI

        :param shutdown: whether or not to shutdown the daemon as well
        :type shutdown: boolean
        """
        def quit_gtkui():
            def shutdown_daemon(result):
                return client.daemon.shutdown()

            def disconnect_client(result):
                return client.disconnect()

            def stop_reactor(result):
                try:
                    reactor.stop()
                except ReactorNotRunning:
                    log.debug("Attempted to stop the reactor but it is not running...")

            def log_failure(failure, action):
                log.error("Encountered error attempting to %s: %s" % \
                          (action, failure.getErrorMessage()))

            d = defer.succeed(None)
            if shutdown:
                d.addCallback(shutdown_daemon)
                d.addErrback(log_failure, "shutdown daemon")
            if not client.is_classicmode() and client.connected():
                d.addCallback(disconnect_client)
                d.addErrback(log_failure, "disconnect client")
            d.addBoth(stop_reactor)

        if self.config["lock_tray"] and not self.visible():
            dialog = PasswordDialog(_("Enter your password to Quit Deluge..."))
            def on_dialog_response(response_id):
                if response_id == gtk.RESPONSE_OK:
                    if self.config["tray_password"] == sha(dialog.get_password()).hexdigest():
                        quit_gtkui()
            dialog.run().addCallback(on_dialog_response)
        else:
            quit_gtkui()

    def load_window_state(self):
        x = self.config["window_x_pos"]
        y = self.config["window_y_pos"]
        w = self.config["window_width"]
        h = self.config["window_height"]
        self.window.move(x, y)
        self.window.resize(w, h)
        if self.config["window_maximized"]:
            self.window.maximize()

    def on_window_configure_event(self, widget, event):
        if not self.config["window_maximized"] and self.visible:
            self.config["window_x_pos"] = self.window.get_position()[0]
            self.config["window_y_pos"] = self.window.get_position()[1]
            self.config["window_width"] = event.width
            self.config["window_height"] = event.height

    def on_window_state_event(self, widget, event):
        if event.changed_mask & gtk.gdk.WINDOW_STATE_MAXIMIZED:
            if event.new_window_state & gtk.gdk.WINDOW_STATE_MAXIMIZED:
                log.debug("pos: %s", self.window.get_position())
                self.config["window_maximized"] = True
            elif not event.new_window_state & gtk.gdk.WINDOW_STATE_WITHDRAWN:
                self.config["window_maximized"] = False
        if event.changed_mask & gtk.gdk.WINDOW_STATE_ICONIFIED:
            if event.new_window_state & gtk.gdk.WINDOW_STATE_ICONIFIED:
                log.debug("MainWindow is minimized..")
                component.pause("TorrentView")
                component.pause("StatusBar")
                self.is_minimized = True
            else:
                log.debug("MainWindow is not minimized..")
                try:
                    component.resume("TorrentView")
                    component.resume("StatusBar")
                except:
                    pass
                self.is_minimized = False
        return False

    def on_window_delete_event(self, widget, event):
        if self.config["close_to_tray"] and self.config["enable_system_tray"]:
            self.hide()
        else:
            self.quit()

        return True

    def on_vpaned_position_event(self, obj, param):
        self.config["window_pane_position"] = self.vpaned.get_position()

    def on_drag_data_received_event(self, widget, drag_context, x, y, selection_data, info, timestamp):
        log.debug("Selection(s) dropped on main window %s", selection_data.data)
        if selection_data.get_uris():
            process_args(selection_data.get_uris())
        else:
            process_args(selection_data.data.split())
        drag_context.finish(True, True)

    def on_expose_event(self, widget, event):
        component.get("SystemTray").blink(False)

    def stop(self):
        self.window.set_title("SharkByte")

    def update(self):
        # Update the window title
        def _on_get_session_status(status):
            download_rate = deluge.common.fsize_short(status["payload_download_rate"])
            upload_rate = deluge.common.fsize_short(status["payload_upload_rate"])
            self.window.set_title("%s%s %s%s - Deluge" % (_("D:"), download_rate, _("U:"), upload_rate))
        if self.config["show_rate_in_title"]:
            client.core.get_session_status(["payload_download_rate", "payload_upload_rate"]).addCallback(_on_get_session_status)

    def _on_set_show_rate_in_title(self, key, value):
        if value:
            self.update()
        else:
            self.window.set_title("SharkByte")

    def on_newversionavailable_event(self, new_version):
        if self.config["show_new_releases"]:
            from deluge.ui.gtkui.new_release_dialog import NewReleaseDialog
            reactor.callLater(5.0, NewReleaseDialog().show, new_version)

    def on_torrentfinished_event(self, torrent_id):
        from deluge.ui.gtkui.notification import Notification
        Notification().notify(torrent_id)

    def is_on_active_workspace(self):
        """Determines if MainWindow is on the active workspace.

        Returns:
            bool: True if on active workspace (or wnck module not available), otherwise False.

        """
        if wnck:
            self.screen.force_update()
            win = wnck.window_get(self.window.window.xid)
            if win:
                active_wksp = win.get_screen().get_active_workspace()
                if active_wksp:
                    return win.is_on_workspace(active_wksp)
                else:
                    return False
        return True
Пример #8
0
class StatusTab(Tab):
    def __init__(self):
        super(StatusTab, self).__init__('Status', 'status_tab', 'status_tab_label')

        self.config = ConfigManager('gtk3ui.conf')

        self.progressbar = self.main_builder.get_object('progressbar')
        self.piecesbar = None

        self.add_tab_widget('summary_availability', fratio, ('distributed_copies',))
        self.add_tab_widget(
            'summary_total_downloaded',
            ftotal_sized,
            ('all_time_download', 'total_payload_download'),
        )
        self.add_tab_widget(
            'summary_total_uploaded',
            ftotal_sized,
            ('total_uploaded', 'total_payload_upload'),
        )
        self.add_tab_widget(
            'summary_download_speed',
            fspeed_max,
            ('download_payload_rate', 'max_download_speed'),
        )
        self.add_tab_widget(
            'summary_upload_speed',
            fspeed_max,
            ('upload_payload_rate', 'max_upload_speed'),
        )
        self.add_tab_widget('summary_seeds', fpeer, ('num_seeds', 'total_seeds'))
        self.add_tab_widget('summary_peers', fpeer, ('num_peers', 'total_peers'))
        self.add_tab_widget('summary_eta', ftime_or_dash, ('eta',))
        self.add_tab_widget('summary_share_ratio', fratio, ('ratio',))
        self.add_tab_widget('summary_active_time', ftime_or_dash, ('active_time',))
        self.add_tab_widget('summary_seed_time', ftime_or_dash, ('seeding_time',))
        self.add_tab_widget(
            'summary_seed_rank', fseed_rank_or_dash, ('seed_rank', 'seeding_time')
        )
        self.add_tab_widget('progressbar', fpcnt, ('progress', 'state', 'message'))
        self.add_tab_widget(
            'summary_last_seen_complete', fdate_or_never, ('last_seen_complete',)
        )
        self.add_tab_widget(
            'summary_last_transfer', ftime_or_dash, ('time_since_transfer',)
        )

        self.config.register_set_function(
            'show_piecesbar', self.on_show_piecesbar_config_changed, apply_now=True
        )

    def update(self):
        # Get the first selected torrent
        selected = component.get('TorrentView').get_selected_torrent()

        if not selected:
            # No torrent is selected in the torrentview
            self.clear()
            return

        # Get the torrent status
        status_keys = self.status_keys
        if self.config['show_piecesbar']:
            status_keys.extend(['pieces', 'num_pieces'])

        component.get('SessionProxy').get_torrent_status(
            selected, status_keys
        ).addCallback(self._on_get_torrent_status)

    def _on_get_torrent_status(self, status):
        # Check to see if we got valid data from the core
        if not status:
            return

        # Update all the label widgets
        for widget in self.tab_widgets.values():
            txt = self.widget_status_as_fstr(widget, status)
            if decode_bytes(widget[0].get_text()) != txt:
                widget[0].set_text(txt)

        # Update progress bar separately as it's a special case (not a label).
        fraction = status['progress'] / 100

        if self.config['show_piecesbar']:
            if self.piecesbar.get_fraction() != fraction:
                self.piecesbar.set_fraction(fraction)
            if (
                status['state'] != 'Checking'
                and self.piecesbar.get_pieces() != status['pieces']
            ):
                # Skip pieces assignment if checking torrent.
                self.piecesbar.set_pieces(status['pieces'], status['num_pieces'])
            self.piecesbar.update()
        else:
            if self.progressbar.get_fraction() != fraction:
                self.progressbar.set_fraction(fraction)

    def on_show_piecesbar_config_changed(self, key, show):
        if show:
            self.show_piecesbar()
        else:
            self.hide_piecesbar()

    def show_piecesbar(self):
        if self.piecesbar is None:
            self.piecesbar = PiecesBar()
            self.main_builder.get_object('status_progress_vbox').pack_start(
                self.piecesbar, False, False, 0
            )
        self.tab_widgets['piecesbar'] = TabWidget(
            self.piecesbar, fpcnt, ('progress', 'state', 'message')
        )
        self.piecesbar.show()
        self.progressbar.hide()

    def hide_piecesbar(self):
        self.progressbar.show()
        if self.piecesbar:
            self.piecesbar.hide()
            self.tab_widgets.pop('piecesbar', None)
            self.piecesbar = None

    def clear(self):
        for widget in self.tab_widgets.values():
            widget[0].set_text('')

        if self.config['show_piecesbar']:
            self.piecesbar.clear()
        else:
            self.progressbar.set_fraction(0)
Пример #9
0
class SystemTray(component.Component):
    def __init__(self):
        component.Component.__init__(self, 'SystemTray', interval=4)
        self.mainwindow = component.get('MainWindow')
        self.config = ConfigManager('gtkui.conf')
        # List of widgets that need to be hidden when not connected to a host
        self.hide_widget_list = [
            'menuitem_add_torrent',
            'menuitem_pause_session',
            'menuitem_resume_session',
            'menuitem_download_limit',
            'menuitem_upload_limit',
            'menuitem_quitdaemon',
            'separatormenuitem1',
            'separatormenuitem2',
            'separatormenuitem3',
            'separatormenuitem4'
        ]
        self.config.register_set_function('enable_system_tray', self.on_enable_system_tray_set)
        # bit of a hack to prevent function from doing something on startup
        self.__enabled_set_once = False
        self.config.register_set_function('enable_appindicator', self.on_enable_appindicator_set)

        self.max_download_speed = -1.0
        self.download_rate = 0.0
        self.max_upload_speed = -1.0
        self.upload_rate = 0.0

        self.config_value_changed_dict = {
            'max_download_speed': self._on_max_download_speed,
            'max_upload_speed': self._on_max_upload_speed
        }

    def enable(self):
        """Enables the system tray icon."""
        self.builder = Builder()
        self.builder.add_from_file(resource_filename('deluge.ui.gtkui', os.path.join(
            'glade', 'tray_menu.ui')))

        self.builder.connect_signals(self)

        self.tray_menu = self.builder.get_object('tray_menu')

        if appindicator and self.config['enable_appindicator']:
            log.debug('Enabling the Application Indicator...')
            self.indicator = appindicator.Indicator('deluge', 'deluge',
                                                    appindicator.CATEGORY_APPLICATION_STATUS)
            try:
                self.indicator.set_property('title', _('Deluge'))
            except TypeError:
                # Catch 'title' property error for previous appindicator versions
                pass
            # Pass the menu to the Application Indicator
            self.indicator.set_menu(self.tray_menu)

            # Make sure the status of the Show Window MenuItem is correct
            self._sig_win_hide = self.mainwindow.window.connect('hide', self._on_window_hide)
            self._sig_win_show = self.mainwindow.window.connect('show', self._on_window_show)
            if self.mainwindow.visible():
                self.builder.get_object('menuitem_show_deluge').set_active(True)
            else:
                self.builder.get_object('menuitem_show_deluge').set_active(False)

            # Show the Application Indicator
            self.indicator.set_status(appindicator.STATUS_ACTIVE)

        else:
            log.debug('Enabling the system tray icon..')
            if windows_check():
                self.tray = status_icon_new_from_pixbuf(get_logo(32))
            else:
                self.tray = status_icon_new_from_icon_name('deluge')

            self.tray.connect('activate', self.on_tray_clicked)
            self.tray.connect('popup-menu', self.on_tray_popup)

        self.builder.get_object('download-limit-image').set_from_file(get_pixmap('downloading16.png'))
        self.builder.get_object('upload-limit-image').set_from_file(get_pixmap('seeding16.png'))

        client.register_event_handler('ConfigValueChangedEvent', self.config_value_changed)
        if client.connected():
            # We're connected so we need to get some values from the core
            self.__start()
        else:
            # Hide menu widgets because we're not connected to a host.
            for widget in self.hide_widget_list:
                self.builder.get_object(widget).hide()

    def __start(self):
        if self.config['enable_system_tray']:

            if self.config['standalone']:
                try:
                    self.hide_widget_list.remove('menuitem_quitdaemon')
                    self.hide_widget_list.remove('separatormenuitem4')
                except ValueError:
                    pass
                self.builder.get_object('menuitem_quitdaemon').hide()
                self.builder.get_object('separatormenuitem4').hide()

            # Show widgets in the hide list because we've connected to a host
            for widget in self.hide_widget_list:
                self.builder.get_object(widget).show()

            # Build the bandwidth speed limit menus
            self.build_tray_bwsetsubmenu()

            # Get some config values
            def update_config_values(configs):
                self._on_max_download_speed(configs['max_download_speed'])
                self._on_max_upload_speed(configs['max_upload_speed'])
            client.core.get_config_values(['max_download_speed', 'max_upload_speed']).addCallback(update_config_values)

    def start(self):
        self.__start()

    def stop(self):
        if self.config['enable_system_tray'] and not self.config['enable_appindicator']:
            try:
                # Hide widgets in hide list because we're not connected to a host
                for widget in self.hide_widget_list:
                    self.builder.get_object(widget).hide()
            except Exception as ex:
                log.debug('Unable to hide system tray menu widgets: %s', ex)

            self.tray.set_tooltip_text(_('Deluge') + '\n' + _('Not Connected...'))

    def shutdown(self):
        if self.config['enable_system_tray']:
            if appindicator and self.config['enable_appindicator']:
                self.indicator.set_status(appindicator.STATUS_PASSIVE)
            else:
                self.tray.set_visible(False)

    def send_status_request(self):
        client.core.get_session_status([
            'payload_upload_rate',
            'payload_download_rate']).addCallback(self._on_get_session_status)

    def config_value_changed(self, key, value):
        """This is called when we received a config_value_changed signal from
        the core."""
        if key in self.config_value_changed_dict:
            self.config_value_changed_dict[key](value)

    def _on_max_download_speed(self, max_download_speed):
        if self.max_download_speed != max_download_speed:
            self.max_download_speed = max_download_speed
            self.build_tray_bwsetsubmenu()

    def _on_max_upload_speed(self, max_upload_speed):
        if self.max_upload_speed != max_upload_speed:
            self.max_upload_speed = max_upload_speed
            self.build_tray_bwsetsubmenu()

    def _on_get_session_status(self, status):
        self.download_rate = fspeed(status['payload_download_rate'], shortform=True)
        self.upload_rate = fspeed(status['payload_upload_rate'], shortform=True)

    def update(self):
        if not self.config['enable_system_tray']:
            return

        # Tool tip text not available for appindicator
        if appindicator and self.config['enable_appindicator']:
            if self.mainwindow.visible():
                self.builder.get_object('menuitem_show_deluge').set_active(True)
            else:
                self.builder.get_object('menuitem_show_deluge').set_active(False)
            return

        # Set the tool tip text
        max_download_speed = self.max_download_speed
        max_upload_speed = self.max_upload_speed

        if max_download_speed == -1:
            max_download_speed = _('Unlimited')
        else:
            max_download_speed = '%s %s' % (max_download_speed, _('K/s'))
        if max_upload_speed == -1:
            max_upload_speed = _('Unlimited')
        else:
            max_upload_speed = '%s %s' % (max_upload_speed, _('K/s'))

        msg = '%s\n%s: %s (%s)\n%s: %s (%s)' % (
            _('Deluge'), _('Down'), self.download_rate,
            max_download_speed, _('Up'), self.upload_rate, max_upload_speed
        )

        # Set the tooltip
        self.tray.set_tooltip_text(msg)

        self.send_status_request()

    def build_tray_bwsetsubmenu(self):
        # Create the Download speed list sub-menu
        submenu_bwdownset = build_menu_radio_list(
            self.config['tray_download_speed_list'], self.on_tray_setbwdown,
            self.max_download_speed,
            _('K/s'), show_notset=True, show_other=True
        )

        # Create the Upload speed list sub-menu
        submenu_bwupset = build_menu_radio_list(
            self.config['tray_upload_speed_list'], self.on_tray_setbwup,
            self.max_upload_speed,
            _('K/s'), show_notset=True, show_other=True
        )
        # Add the sub-menus to the tray menu
        self.builder.get_object('menuitem_download_limit').set_submenu(
            submenu_bwdownset)
        self.builder.get_object('menuitem_upload_limit').set_submenu(
            submenu_bwupset)

        # Show the sub-menus for all to see
        submenu_bwdownset.show_all()
        submenu_bwupset.show_all()

    def disable(self, invert_app_ind_conf=False):
        """Disables the system tray icon or appindicator."""
        try:
            if invert_app_ind_conf:
                app_ind_conf = not self.config['enable_appindicator']
            else:
                app_ind_conf = self.config['enable_appindicator']
            if appindicator and app_ind_conf:
                if hasattr(self, '_sig_win_hide'):
                    self.mainwindow.window.disconnect(self._sig_win_hide)
                    self.mainwindow.window.disconnect(self._sig_win_show)
                    log.debug('Disabling the application indicator..')

                self.indicator.set_status(appindicator.STATUS_PASSIVE)
                del self.indicator
            else:
                log.debug('Disabling the system tray icon..')
                self.tray.set_visible(False)
                del self.tray
            del self.builder
            del self.tray_menu
        except Exception as ex:
            log.debug('Unable to disable system tray: %s', ex)

    def blink(self, value):
        try:
            self.tray.set_blinking(value)
        except AttributeError:
            # If self.tray is not defined then ignore. This happens when the
            # tray icon is not being used.
            pass

    def on_enable_system_tray_set(self, key, value):
        """Called whenever the 'enable_system_tray' config key is modified"""
        if value:
            self.enable()
        else:
            self.disable()

    def on_enable_appindicator_set(self, key, value):
        """Called whenever the 'enable_appindicator' config key is modified"""
        if self.__enabled_set_once:
            self.disable(True)
            self.enable()
        self.__enabled_set_once = True

    def on_tray_clicked(self, icon):
        """Called when the tray icon is left clicked."""
        self.blink(False)

        if self.mainwindow.active():
            self.mainwindow.hide()
        else:
            self.mainwindow.present()

    def on_tray_popup(self, status_icon, button, activate_time):
        """Called when the tray icon is right clicked."""
        self.blink(False)

        if self.mainwindow.visible():
            self.builder.get_object('menuitem_show_deluge').set_active(True)
        else:
            self.builder.get_object('menuitem_show_deluge').set_active(False)

        popup_function = status_icon_position_menu
        if windows_check() or osx_check():
            popup_function = None
            button = 0
        self.tray_menu.popup(None, None, popup_function, button, activate_time, status_icon)

    def on_menuitem_show_deluge_activate(self, menuitem):
        log.debug('on_menuitem_show_deluge_activate')
        if menuitem.get_active() and not self.mainwindow.visible():
            self.mainwindow.present()
        elif not menuitem.get_active() and self.mainwindow.visible():
            self.mainwindow.hide()

    def on_menuitem_add_torrent_activate(self, menuitem):
        log.debug('on_menuitem_add_torrent_activate')
        component.get('AddTorrentDialog').show()

    def on_menuitem_pause_session_activate(self, menuitem):
        log.debug('on_menuitem_pause_session_activate')
        client.core.pause_session()

    def on_menuitem_resume_session_activate(self, menuitem):
        log.debug('on_menuitem_resume_session_activate')
        client.core.resume_session()

    def on_menuitem_quit_activate(self, menuitem):
        log.debug('on_menuitem_quit_activate')
        self.mainwindow.quit()

    def on_menuitem_quitdaemon_activate(self, menuitem):
        log.debug('on_menuitem_quitdaemon_activate')
        self.mainwindow.quit(shutdown=True)

    def on_tray_setbwdown(self, widget, data=None):
        if isinstance(widget, RadioMenuItem):
            # ignore previous radiomenuitem value
            if not widget.get_active():
                return
        self.setbwlimit(widget, _('Download Speed Limit'), _('Set the maximum download speed'),
                        'max_download_speed', 'tray_download_speed_list', self.max_download_speed,
                        'downloading.svg')

    def on_tray_setbwup(self, widget, data=None):
        if isinstance(widget, RadioMenuItem):
            # ignore previous radiomenuitem value
            if not widget.get_active():
                return
        self.setbwlimit(widget, _('Upload Speed Limit'), _('Set the maximum upload speed'),
                        'max_upload_speed', 'tray_upload_speed_list', self.max_upload_speed,
                        'seeding.svg')

    def _on_window_hide(self, widget, data=None):
        """_on_window_hide - update the menuitem's status"""
        log.debug('_on_window_hide')
        self.builder.get_object('menuitem_show_deluge').set_active(False)

    def _on_window_show(self, widget, data=None):
        """_on_window_show - update the menuitem's status"""
        log.debug('_on_window_show')
        self.builder.get_object('menuitem_show_deluge').set_active(True)

    def setbwlimit(self, widget, header, text, core_key, ui_key, default, image):
        """Sets the bandwidth limit based on the user selection."""
        def set_value(value):
            log.debug('setbwlimit: %s', value)
            if value is None:
                return
            elif value == 0:
                value = -1
            # Set the config in the core
            client.core.set_config({core_key: value})

        if widget.get_name() == 'unlimited':
            set_value(-1)
        elif widget.get_name() == 'other':
            dialog = dialogs.OtherDialog(header, text, _('K/s'), image, default)
            dialog.run().addCallback(set_value)
        else:
            set_value(widget.get_children()[0].get_text().split(' ')[0])
Пример #10
0
class TorrentManager(component.Component):
    """
    TorrentManager contains a list of torrents in the current libtorrent
    session.  This object is also responsible for saving the state of the
    session for use on restart.
    """

    def __init__(self):
        component.Component.__init__(self, "TorrentManager", interval=5,
                                     depend=["CorePluginManager", "AlertManager"])
        log.debug("TorrentManager init..")
        # Set the libtorrent session
        self.session = component.get("Core").session
        # Set the alertmanager
        self.alerts = component.get("AlertManager")
        # Get the core config
        self.config = ConfigManager("core.conf")

        # Make sure the state folder has been created
        if not os.path.exists(os.path.join(get_config_dir(), "state")):
            os.makedirs(os.path.join(get_config_dir(), "state"))

        # Create the torrents dict { torrent_id: Torrent }
        self.torrents = {}
        self.queued_torrents = set()

        # This is a map of torrent_ids to Deferreds used to track needed resume data.
        # The Deferreds will be completed when resume data has been saved.
        self.waiting_on_resume_data = {}

        # Keeps track of resume data
        self.resume_data = {}

        self.torrents_status_requests = []
        self.status_dict = {}
        self.last_state_update_alert_ts = 0

        # Register set functions
        self.config.register_set_function("max_connections_per_torrent",
            self.on_set_max_connections_per_torrent)
        self.config.register_set_function("max_upload_slots_per_torrent",
            self.on_set_max_upload_slots_per_torrent)
        self.config.register_set_function("max_upload_speed_per_torrent",
            self.on_set_max_upload_speed_per_torrent)
        self.config.register_set_function("max_download_speed_per_torrent",
            self.on_set_max_download_speed_per_torrent)

        # Register alert functions
        self.alerts.register_handler("torrent_finished_alert",
            self.on_alert_torrent_finished)
        self.alerts.register_handler("torrent_paused_alert",
            self.on_alert_torrent_paused)
        self.alerts.register_handler("torrent_checked_alert",
            self.on_alert_torrent_checked)
        self.alerts.register_handler("tracker_reply_alert",
            self.on_alert_tracker_reply)
        self.alerts.register_handler("tracker_announce_alert",
            self.on_alert_tracker_announce)
        self.alerts.register_handler("tracker_warning_alert",
            self.on_alert_tracker_warning)
        self.alerts.register_handler("tracker_error_alert",
            self.on_alert_tracker_error)
        self.alerts.register_handler("storage_moved_alert",
            self.on_alert_storage_moved)
        self.alerts.register_handler("torrent_resumed_alert",
            self.on_alert_torrent_resumed)
        self.alerts.register_handler("state_changed_alert",
            self.on_alert_state_changed)
        self.alerts.register_handler("save_resume_data_alert",
            self.on_alert_save_resume_data)
        self.alerts.register_handler("save_resume_data_failed_alert",
            self.on_alert_save_resume_data_failed)
        self.alerts.register_handler("file_renamed_alert",
            self.on_alert_file_renamed)
        self.alerts.register_handler("metadata_received_alert",
            self.on_alert_metadata_received)
        self.alerts.register_handler("file_error_alert",
            self.on_alert_file_error)
        self.alerts.register_handler("file_completed_alert",
            self.on_alert_file_completed)
        self.alerts.register_handler("state_update_alert",
            self.on_alert_state_update)

    def start(self):
        # Get the pluginmanager reference
        self.plugins = component.get("CorePluginManager")

        # Run the old state upgrader before loading state
        deluge.core.oldstateupgrader.OldStateUpgrader()

        # Try to load the state from file
        self.load_state()

        # Save the state periodically
        self.save_state_timer = LoopingCall(self.save_state)
        self.save_state_timer.start(200, False)
        self.save_resume_data_timer = LoopingCall(self.save_resume_data)
        self.save_resume_data_timer.start(190, False)
        # Force update for all resume data a bit less frequently
        self.save_all_resume_data_timer = LoopingCall(self.save_resume_data, self.torrents.keys())
        self.save_all_resume_data_timer.start(900, False)

    def stop(self):
        # Stop timers
        if self.save_state_timer.running:
            self.save_state_timer.stop()

        if self.save_resume_data_timer.running:
            self.save_resume_data_timer.stop()

        if self.save_all_resume_data_timer.running:
            self.save_all_resume_data_timer.stop()

        # Save state on shutdown
        self.save_state()

        self.session.pause()
        for key in self.torrents:
            # Stop the status cleanup LoopingCall here
            self.torrents[key].prev_status_cleanup_loop.stop()

        return self.save_resume_data(self.torrents.keys())

    def update(self):
        for torrent_id, torrent in self.torrents.items():
            if torrent.options["stop_at_ratio"] and torrent.state not in (
                                "Checking", "Allocating", "Paused", "Queued"):
                # If the global setting is set, but the per-torrent isn't..
                # Just skip to the next torrent.
                # This is so that a user can turn-off the stop at ratio option
                # on a per-torrent basis
                if not torrent.options["stop_at_ratio"]:
                    continue
                if torrent.get_ratio() >= torrent.options["stop_ratio"] and torrent.is_finished:
                    if torrent.options["remove_at_ratio"]:
                        self.remove(torrent_id)
                        break
                    if not torrent.handle.is_paused():
                        torrent.pause()

    def __getitem__(self, torrent_id):
        """Return the Torrent with torrent_id"""
        return self.torrents[torrent_id]

    def get_torrent_list(self):
        """Returns a list of torrent_ids"""
        torrent_ids = self.torrents.keys()
        if component.get("RPCServer").get_session_auth_level() == AUTH_LEVEL_ADMIN:
            return torrent_ids

        current_user = component.get("RPCServer").get_session_user()
        for torrent_id in torrent_ids[:]:
            torrent_status = self[torrent_id].get_status(["owner", "shared"])
            if torrent_status["owner"] != current_user and torrent_status["shared"] == False:
                torrent_ids.pop(torrent_ids.index(torrent_id))
        return torrent_ids

    def get_torrent_info_from_file(self, filepath):
        """Returns a torrent_info for the file specified or None"""
        torrent_info = None
        # Get the torrent data from the torrent file
        try:
            if log.isEnabledFor(logging.DEBUG):
                log.debug("Attempting to create torrent_info from %s", filepath)
            _file = open(filepath, "rb")
            torrent_info = lt.torrent_info(lt.bdecode(_file.read()))
            _file.close()
        except (IOError, RuntimeError), e:
            log.warning("Unable to open %s: %s", filepath, e)

        return torrent_info
Пример #11
0
class ToolBar(component.Component):
    def __init__(self):
        component.Component.__init__(self, "ToolBar")
        log.debug("ToolBar Init..")
        self.window = component.get("MainWindow")
        self.toolbar = self.window.main_glade.get_widget("toolbar")
        self.config = ConfigManager("gtkui.conf")
        ### Connect Signals ###
        self.window.main_glade.signal_autoconnect({
            "on_toolbutton_add_clicked": self.on_toolbutton_add_clicked,
            "on_toolbutton_remove_clicked": self.on_toolbutton_remove_clicked,
            "on_toolbutton_pause_clicked": self.on_toolbutton_pause_clicked,
            "on_toolbutton_resume_clicked": self.on_toolbutton_resume_clicked,
            "on_toolbutton_preferences_clicked": \
                self.on_toolbutton_preferences_clicked,
            "on_toolbutton_connectionmanager_clicked": \
                self.on_toolbutton_connectionmanager_clicked,
            "on_toolbutton_queue_up_clicked": self.on_toolbutton_queue_up_clicked,
            "on_toolbutton_queue_down_clicked": self.on_toolbutton_queue_down_clicked
        })
        self.change_sensitivity = [
            "toolbutton_add",
            "toolbutton_remove",
            "toolbutton_pause",
            "toolbutton_resume",
            "toolbutton_queue_up",
            "toolbutton_queue_down"
        ]

        self.config.register_set_function("classic_mode", self._on_classic_mode, True)

        # Hide if necessary
        self.visible(self.config["show_toolbar"])

    def start(self):
        if not self.config["classic_mode"]:
            self.window.main_glade.get_widget("toolbutton_connectionmanager").show()

        for widget in self.change_sensitivity:
            self.window.main_glade.get_widget(widget).set_sensitive(True)

    def stop(self):
        for widget in self.change_sensitivity:
            self.window.main_glade.get_widget(widget).set_sensitive(False)

    def visible(self, visible):
        if visible:
            self.toolbar.show()
        else:
            self.toolbar.hide()

        self.config["show_toolbar"] = visible

    def add_toolbutton(self, callback, label=None, image=None, stock=None,
                                                         tooltip=None):
        """Adds a toolbutton to the toolbar"""
        # Create the button
        toolbutton = gtk.ToolButton()
        if stock is not None:
            toolbutton.set_stock_id(stock)
        if label is not None:
            toolbutton.set_label(label)
        if image is not None:
            toolbutton.set_icon_widget(image)
        # Set the tooltip
        if tooltip is not None:
            toolbutton.set_tooltip_text(tooltip)
        # Connect the 'clicked' event callback
        toolbutton.connect("clicked", callback)

        # Append the button to the toolbar
        self.toolbar.insert(toolbutton, -1)

        # Show the new toolbutton
        toolbutton.show_all()

        return toolbutton

    def add_separator(self, position=None):
        """Adds a separator toolitem"""
        sep = gtk.SeparatorToolItem()
        if position is not None:
            self.toolbar.insert(sep, position)
        else:
            # Append the separator
            self.toolbar.insert(sep, -1)

        sep.show()

        return sep

    def remove(self, widget):
        """Removes a widget from the toolbar"""
        self.toolbar.remove(widget)

    ### Callbacks ###
    def on_toolbutton_add_clicked(self, data):
        log.debug("on_toolbutton_add_clicked")
        # Use the menubar's callback
        component.get("MenuBar").on_menuitem_addtorrent_activate(data)

    def on_toolbutton_remove_clicked(self, data):
        log.debug("on_toolbutton_remove_clicked")
        # Use the menubar's callbacks
        component.get("MenuBar").on_menuitem_remove_activate(data)

    def on_toolbutton_pause_clicked(self, data):
        log.debug("on_toolbutton_pause_clicked")
        # Use the menubar's callbacks
        component.get("MenuBar").on_menuitem_pause_activate(data)

    def on_toolbutton_resume_clicked(self, data):
        log.debug("on_toolbutton_resume_clicked")
        # Use the menubar's calbacks
        component.get("MenuBar").on_menuitem_resume_activate(data)

    def on_toolbutton_preferences_clicked(self, data):
        log.debug("on_toolbutton_preferences_clicked")
        # Use the menubar's callbacks
        component.get("MenuBar").on_menuitem_preferences_activate(data)

    def on_toolbutton_connectionmanager_clicked(self, data):
        log.debug("on_toolbutton_connectionmanager_clicked")
        # Use the menubar's callbacks
        component.get("MenuBar").on_menuitem_connectionmanager_activate(data)

    def on_toolbutton_queue_up_clicked(self, data):
        log.debug("on_toolbutton_queue_up_clicked")
        component.get("MenuBar").on_menuitem_queue_up_activate(data)

    def on_toolbutton_queue_down_clicked(self, data):
        log.debug("on_toolbutton_queue_down_clicked")
        component.get("MenuBar").on_menuitem_queue_down_activate(data)

    def _on_classic_mode(self, key, value):
        w = self.window.main_glade.get_widget("toolbutton_connectionmanager")
        if value:
            w.hide()
        else:
            w.show()
Пример #12
0
class ToolBar(component.Component):
    def __init__(self):
        component.Component.__init__(self, "ToolBar")
        log.debug("ToolBar Init..")
        self.window = component.get("MainWindow")
        self.toolbar = self.window.main_glade.get_widget("toolbar")
        self.config = ConfigManager("gtkui.conf")
        ### Connect Signals ###
        self.window.main_glade.signal_autoconnect({
            "on_toolbutton_add_clicked": self.on_toolbutton_add_clicked,
            "on_toolbutton_remove_clicked": self.on_toolbutton_remove_clicked,
            "on_toolbutton_pause_clicked": self.on_toolbutton_pause_clicked,
            "on_toolbutton_resume_clicked": self.on_toolbutton_resume_clicked,
            "on_toolbutton_preferences_clicked": \
                self.on_toolbutton_preferences_clicked,
            "on_toolbutton_connectionmanager_clicked": \
                self.on_toolbutton_connectionmanager_clicked,
            "on_toolbutton_queue_up_clicked": self.on_toolbutton_queue_up_clicked,
            "on_toolbutton_queue_down_clicked": self.on_toolbutton_queue_down_clicked
        })
        self.change_sensitivity = [
            "toolbutton_add", "toolbutton_remove", "toolbutton_pause",
            "toolbutton_resume", "toolbutton_queue_up",
            "toolbutton_queue_down", "toolbutton_filter", "find_menuitem"
        ]

        self.config.register_set_function("classic_mode",
                                          self._on_classic_mode, True)

        # Hide if necessary
        self.visible(self.config["show_toolbar"])

    def start(self):
        if not self.config["classic_mode"]:
            self.window.main_glade.get_widget(
                "toolbutton_connectionmanager").show()

        for widget in self.change_sensitivity:
            self.window.main_glade.get_widget(widget).set_sensitive(True)

    def stop(self):
        for widget in self.change_sensitivity:
            self.window.main_glade.get_widget(widget).set_sensitive(False)

    def visible(self, visible):
        if visible:
            self.toolbar.show()
        else:
            self.toolbar.hide()

        self.config["show_toolbar"] = visible

    def add_toolbutton(self,
                       callback,
                       label=None,
                       image=None,
                       stock=None,
                       tooltip=None):
        """Adds a toolbutton to the toolbar"""
        # Create the button
        toolbutton = gtk.ToolButton()
        if stock is not None:
            toolbutton.set_stock_id(stock)
        if label is not None:
            toolbutton.set_label(label)
        if image is not None:
            toolbutton.set_icon_widget(image)
        # Set the tooltip
        if tooltip is not None:
            toolbutton.set_tooltip_text(tooltip)
        # Connect the 'clicked' event callback
        toolbutton.connect("clicked", callback)

        # Append the button to the toolbar
        self.toolbar.insert(toolbutton, -1)

        # Show the new toolbutton
        toolbutton.show_all()

        return toolbutton

    def add_separator(self, position=None):
        """Adds a separator toolitem"""
        sep = gtk.SeparatorToolItem()
        if position is not None:
            self.toolbar.insert(sep, position)
        else:
            # Append the separator
            self.toolbar.insert(sep, -1)

        sep.show()

        return sep

    def remove(self, widget):
        """Removes a widget from the toolbar"""
        self.toolbar.remove(widget)

    ### Callbacks ###
    def on_toolbutton_add_clicked(self, data):
        log.debug("on_toolbutton_add_clicked")
        # Use the menubar's callback
        component.get("MenuBar").on_menuitem_addtorrent_activate(data)

    def on_toolbutton_remove_clicked(self, data):
        log.debug("on_toolbutton_remove_clicked")
        # Use the menubar's callbacks
        component.get("MenuBar").on_menuitem_remove_activate(data)

    def on_toolbutton_pause_clicked(self, data):
        log.debug("on_toolbutton_pause_clicked")
        # Use the menubar's callbacks
        component.get("MenuBar").on_menuitem_pause_activate(data)

    def on_toolbutton_resume_clicked(self, data):
        log.debug("on_toolbutton_resume_clicked")
        # Use the menubar's calbacks
        component.get("MenuBar").on_menuitem_resume_activate(data)

    def on_toolbutton_preferences_clicked(self, data):
        log.debug("on_toolbutton_preferences_clicked")
        # Use the menubar's callbacks
        component.get("MenuBar").on_menuitem_preferences_activate(data)

    def on_toolbutton_connectionmanager_clicked(self, data):
        log.debug("on_toolbutton_connectionmanager_clicked")
        # Use the menubar's callbacks
        component.get("MenuBar").on_menuitem_connectionmanager_activate(data)

    def on_toolbutton_queue_up_clicked(self, data):
        log.debug("on_toolbutton_queue_up_clicked")
        component.get("MenuBar").on_menuitem_queue_up_activate(data)

    def on_toolbutton_queue_down_clicked(self, data):
        log.debug("on_toolbutton_queue_down_clicked")
        component.get("MenuBar").on_menuitem_queue_down_activate(data)

    def _on_classic_mode(self, key, value):
        w = self.window.main_glade.get_widget("toolbutton_connectionmanager")
        if value:
            w.hide()
        else:
            w.show()
Пример #13
0
class TorrentManager(component.Component):
    """
    TorrentManager contains a list of torrents in the current libtorrent
    session.  This object is also responsible for saving the state of the
    session for use on restart.
    """
    def __init__(self):
        component.Component.__init__(
            self,
            "TorrentManager",
            interval=5,
            depend=["CorePluginManager", "AlertManager"])
        log.debug("TorrentManager init..")
        # Set the libtorrent session
        self.session = component.get("Core").session
        # Set the alertmanager
        self.alerts = component.get("AlertManager")
        # Get the core config
        self.config = ConfigManager("core.conf")

        # Make sure the state folder has been created
        if not os.path.exists(os.path.join(get_config_dir(), "state")):
            os.makedirs(os.path.join(get_config_dir(), "state"))

        # Create the torrents dict { torrent_id: Torrent }
        self.torrents = {}
        self.queued_torrents = set()

        # This is a map of torrent_ids to Deferreds used to track needed resume data.
        # The Deferreds will be completed when resume data has been saved.
        self.waiting_on_resume_data = {}

        # Keeps track of resume data
        self.resume_data = {}

        self.torrents_status_requests = []
        self.status_dict = {}
        self.last_state_update_alert_ts = 0

        # Register set functions
        self.config.register_set_function(
            "max_connections_per_torrent",
            self.on_set_max_connections_per_torrent)
        self.config.register_set_function(
            "max_upload_slots_per_torrent",
            self.on_set_max_upload_slots_per_torrent)
        self.config.register_set_function(
            "max_upload_speed_per_torrent",
            self.on_set_max_upload_speed_per_torrent)
        self.config.register_set_function(
            "max_download_speed_per_torrent",
            self.on_set_max_download_speed_per_torrent)

        # Register alert functions
        self.alerts.register_handler("torrent_finished_alert",
                                     self.on_alert_torrent_finished)
        self.alerts.register_handler("torrent_paused_alert",
                                     self.on_alert_torrent_paused)
        self.alerts.register_handler("torrent_checked_alert",
                                     self.on_alert_torrent_checked)
        self.alerts.register_handler("tracker_reply_alert",
                                     self.on_alert_tracker_reply)
        self.alerts.register_handler("tracker_announce_alert",
                                     self.on_alert_tracker_announce)
        self.alerts.register_handler("tracker_warning_alert",
                                     self.on_alert_tracker_warning)
        self.alerts.register_handler("tracker_error_alert",
                                     self.on_alert_tracker_error)
        self.alerts.register_handler("storage_moved_alert",
                                     self.on_alert_storage_moved)
        self.alerts.register_handler("torrent_resumed_alert",
                                     self.on_alert_torrent_resumed)
        self.alerts.register_handler("state_changed_alert",
                                     self.on_alert_state_changed)
        self.alerts.register_handler("save_resume_data_alert",
                                     self.on_alert_save_resume_data)
        self.alerts.register_handler("save_resume_data_failed_alert",
                                     self.on_alert_save_resume_data_failed)
        self.alerts.register_handler("file_renamed_alert",
                                     self.on_alert_file_renamed)
        self.alerts.register_handler("metadata_received_alert",
                                     self.on_alert_metadata_received)
        self.alerts.register_handler("file_error_alert",
                                     self.on_alert_file_error)
        self.alerts.register_handler("file_completed_alert",
                                     self.on_alert_file_completed)
        self.alerts.register_handler("state_update_alert",
                                     self.on_alert_state_update)

    def start(self):
        # Get the pluginmanager reference
        self.plugins = component.get("CorePluginManager")

        # Run the old state upgrader before loading state
        deluge.core.oldstateupgrader.OldStateUpgrader()

        # Try to load the state from file
        self.load_state()

        # Save the state periodically
        self.save_state_timer = LoopingCall(self.save_state)
        self.save_state_timer.start(200, False)
        self.save_resume_data_timer = LoopingCall(self.save_resume_data)
        self.save_resume_data_timer.start(190, False)
        # Force update for all resume data a bit less frequently
        self.save_all_resume_data_timer = LoopingCall(self.save_resume_data,
                                                      self.torrents.keys())
        self.save_all_resume_data_timer.start(900, False)

    def stop(self):
        # Stop timers
        if self.save_state_timer.running:
            self.save_state_timer.stop()

        if self.save_resume_data_timer.running:
            self.save_resume_data_timer.stop()

        if self.save_all_resume_data_timer.running:
            self.save_all_resume_data_timer.stop()

        # Save state on shutdown
        self.save_state()

        self.session.pause()
        for key in self.torrents:
            # Stop the status cleanup LoopingCall here
            self.torrents[key].prev_status_cleanup_loop.stop()

        return self.save_resume_data(self.torrents.keys())

    def update(self):
        for torrent_id, torrent in self.torrents.items():
            if torrent.options["stop_at_ratio"] and torrent.state not in (
                    "Checking", "Allocating", "Paused", "Queued"):
                # If the global setting is set, but the per-torrent isn't..
                # Just skip to the next torrent.
                # This is so that a user can turn-off the stop at ratio option
                # on a per-torrent basis
                if not torrent.options["stop_at_ratio"]:
                    continue
                if torrent.get_ratio(
                ) >= torrent.options["stop_ratio"] and torrent.is_finished:
                    if torrent.options["remove_at_ratio"]:
                        self.remove(torrent_id)
                        break
                    if not torrent.handle.is_paused():
                        torrent.pause()

    def __getitem__(self, torrent_id):
        """Return the Torrent with torrent_id"""
        return self.torrents[torrent_id]

    def get_torrent_list(self):
        """Returns a list of torrent_ids"""
        torrent_ids = self.torrents.keys()
        if component.get(
                "RPCServer").get_session_auth_level() == AUTH_LEVEL_ADMIN:
            return torrent_ids

        current_user = component.get("RPCServer").get_session_user()
        for torrent_id in torrent_ids[:]:
            torrent_status = self[torrent_id].get_status(["owner", "shared"])
            if torrent_status["owner"] != current_user and torrent_status[
                    "shared"] == False:
                torrent_ids.pop(torrent_ids.index(torrent_id))
        return torrent_ids

    def get_torrent_info_from_file(self, filepath):
        """Returns a torrent_info for the file specified or None"""
        torrent_info = None
        # Get the torrent data from the torrent file
        try:
            if log.isEnabledFor(logging.DEBUG):
                log.debug("Attempting to create torrent_info from %s",
                          filepath)
            _file = open(filepath, "rb")
            torrent_info = lt.torrent_info(lt.bdecode(_file.read()))
            _file.close()
        except (IOError, RuntimeError), e:
            log.warning("Unable to open %s: %s", filepath, e)

        return torrent_info
Пример #14
0
class MainWindow(component.Component):
    def __init__(self):
        component.Component.__init__(self, "MainWindow", interval=2)
        self.config = ConfigManager("gtkui.conf")
        self.gtk_builder_signals_holder = _GtkBuilderSignalsHolder()
        self.main_builder = gtk.Builder()
        # Patch this GtkBuilder to avoid connecting signals from elsewhere
        #
        # Think about splitting up the main window gtkbuilder file into the necessary parts
        # in order not to have to monkey patch GtkBuilder. Those parts would then need to
        # be added to the main window "by hand".
        self.main_builder.prev_connect_signals = copy.deepcopy(
            self.main_builder.connect_signals)

        def patched_connect_signals(*a, **k):
            raise RuntimeError(
                "In order to connect signals to this GtkBuilder instance please use "
                "'component.get(\"MainWindow\").connect_signals()'")

        self.main_builder.connect_signals = patched_connect_signals

        # Get the gtk builder file for the main window
        self.main_builder.add_from_file(
            deluge.common.resource_filename(
                "deluge.ui.gtkui", os.path.join("glade", "main_window.ui")))
        # The new release dialog
        self.main_builder.add_from_file(
            deluge.common.resource_filename(
                "deluge.ui.gtkui",
                os.path.join("glade", "main_window.new_release.ui")))
        # The move storage dialog
        self.main_builder.add_from_file(
            deluge.common.resource_filename(
                "deluge.ui.gtkui",
                os.path.join("glade", "main_window.move_storage.ui")))
        # The tabs
        self.main_builder.add_from_file(
            deluge.common.resource_filename(
                "deluge.ui.gtkui", os.path.join("glade",
                                                "main_window.tabs.ui")))
        # The tabs file menu
        self.main_builder.add_from_file(
            deluge.common.resource_filename(
                "deluge.ui.gtkui",
                os.path.join("glade", "main_window.tabs.menu_file.ui")))
        # The tabs peer menu
        self.main_builder.add_from_file(
            deluge.common.resource_filename(
                "deluge.ui.gtkui",
                os.path.join("glade", "main_window.tabs.menu_peer.ui")))

        self.window = self.main_builder.get_object("main_window")

        self.window.set_icon(common.get_deluge_icon())

        self.vpaned = self.main_builder.get_object("vpaned")
        self.initial_vpaned_position = self.config["window_pane_position"]

        # Load the window state
        self.load_window_state()

        # Keep track of window's minimization state so that we don't update the
        # UI when it is minimized.
        self.is_minimized = False

        self.window.drag_dest_set(gtk.DEST_DEFAULT_ALL,
                                  [('text/uri-list', 0, 80)],
                                  gtk.gdk.ACTION_COPY)

        # Connect events
        self.window.connect("window-state-event", self.on_window_state_event)
        self.window.connect("configure-event", self.on_window_configure_event)
        self.window.connect("delete-event", self.on_window_delete_event)
        self.window.connect("drag-data-received",
                            self.on_drag_data_received_event)
        self.vpaned.connect("notify::position", self.on_vpaned_position_event)
        self.window.connect("expose-event", self.on_expose_event)

        self.config.register_set_function("show_rate_in_title",
                                          self._on_set_show_rate_in_title,
                                          apply_now=False)

        client.register_event_handler("NewVersionAvailableEvent",
                                      self.on_newversionavailable_event)
        client.register_event_handler("TorrentFinishedEvent",
                                      self.on_torrentfinished_event)

    def connect_signals(self, mapping_or_class):
        self.gtk_builder_signals_holder.connect_signals(mapping_or_class)

    def first_show(self):
        if not(self.config["start_in_tray"] and \
               self.config["enable_system_tray"]) and not \
                self.window.get_property("visible"):
            log.debug("Showing window")
            self.main_builder.prev_connect_signals(
                self.gtk_builder_signals_holder)
            self.show()
            while gtk.events_pending():
                gtk.main_iteration(False)
            self.vpaned.set_position(self.initial_vpaned_position)

    def show(self):
        try:
            component.resume("TorrentView")
            component.resume("StatusBar")
            component.resume("TorrentDetails")
        except:
            pass

        self.window.show()

    def hide(self):
        component.pause("TorrentView")
        component.get("TorrentView").save_state()
        component.pause("StatusBar")
        component.pause("TorrentDetails")
        # Store the x, y positions for when we restore the window
        self.window_x_pos = self.window.get_position()[0]
        self.window_y_pos = self.window.get_position()[1]
        self.window.hide()

    def present(self):
        # Restore the proper x,y coords for the window prior to showing it
        try:
            self.config["window_x_pos"] = self.window_x_pos
            self.config["window_y_pos"] = self.window_y_pos
        except:
            pass
        try:
            component.resume("TorrentView")
            component.resume("StatusBar")
            component.resume("TorrentDetails")
        except:
            pass

        self.window.present()
        self.load_window_state()

    def active(self):
        """Returns True if the window is active, False if not."""
        return self.window.is_active()

    def visible(self):
        """Returns True if window is visible, False if not."""
        return self.window.get_property("visible")

    def get_builder(self):
        """Returns a reference to the main window GTK builder object."""
        return self.main_builder

    def quit(self, shutdown=False):
        """
        Quits the GtkUI

        :param shutdown: whether or not to shutdown the daemon as well
        :type shutdown: boolean
        """
        if shutdown:

            def on_daemon_shutdown(result):
                try:
                    reactor.stop()
                except ReactorNotRunning:
                    log.debug(
                        "Attempted to stop the reactor but it is not running..."
                    )

            client.daemon.shutdown().addCallback(on_daemon_shutdown)
            return
        if client.is_classicmode():
            reactor.stop()
            return
        if not client.connected():
            reactor.stop()
            return

        def on_client_disconnected(result):
            reactor.stop()

        client.disconnect().addCallback(on_client_disconnected)

    def load_window_state(self):
        x = self.config["window_x_pos"]
        y = self.config["window_y_pos"]
        w = self.config["window_width"]
        h = self.config["window_height"]
        self.window.move(x, y)
        self.window.resize(w, h)
        if self.config["window_maximized"]:
            self.window.maximize()

    def on_window_configure_event(self, widget, event):
        if not self.config["window_maximized"] and self.visible:
            self.config["window_x_pos"] = self.window.get_position()[0]
            self.config["window_y_pos"] = self.window.get_position()[1]
            self.config["window_width"] = event.width
            self.config["window_height"] = event.height

    def on_window_state_event(self, widget, event):
        if event.changed_mask & gtk.gdk.WINDOW_STATE_MAXIMIZED:
            if event.new_window_state & gtk.gdk.WINDOW_STATE_MAXIMIZED:
                log.debug("pos: %s", self.window.get_position())
                self.config["window_maximized"] = True
            elif not event.new_window_state & gtk.gdk.WINDOW_STATE_WITHDRAWN:
                self.config["window_maximized"] = False
        if event.changed_mask & gtk.gdk.WINDOW_STATE_ICONIFIED:
            if event.new_window_state & gtk.gdk.WINDOW_STATE_ICONIFIED:
                log.debug("MainWindow is minimized..")
                component.pause("TorrentView")
                component.pause("StatusBar")
                self.is_minimized = True
            else:
                log.debug("MainWindow is not minimized..")
                try:
                    component.resume("TorrentView")
                    component.resume("StatusBar")
                except:
                    pass
                self.is_minimized = False
        return False

    def on_window_delete_event(self, widget, event):
        if self.config["close_to_tray"] and self.config["enable_system_tray"]:
            self.hide()
        else:
            self.quit()

        return True

    def on_vpaned_position_event(self, obj, param):
        self.config["window_pane_position"] = self.vpaned.get_position()

    def on_drag_data_received_event(self, widget, drag_context, x, y,
                                    selection_data, info, timestamp):
        log.debug("Selection(s) dropped on main window %s",
                  selection_data.data)
        if selection_data.get_uris():
            process_args(selection_data.get_uris())
        else:
            process_args(selection_data.data.split())
        drag_context.finish(True, True)

    def on_expose_event(self, widget, event):
        component.get("SystemTray").blink(False)

    def stop(self):
        self.window.set_title("Deluge")

    def update(self):
        # Update the window title
        def _on_get_session_status(status):
            download_rate = deluge.common.fsize_short(
                status["payload_download_rate"])
            upload_rate = deluge.common.fsize_short(
                status["payload_upload_rate"])
            self.window.set_title(
                "%s%s %s%s - Deluge" %
                (_("D:"), download_rate, _("U:"), upload_rate))

        if self.config["show_rate_in_title"]:
            client.core.get_session_status(
                ["payload_download_rate",
                 "payload_upload_rate"]).addCallback(_on_get_session_status)

    def _on_set_show_rate_in_title(self, key, value):
        if value:
            self.update()
        else:
            self.window.set_title("Deluge")

    def on_newversionavailable_event(self, new_version):
        if self.config["show_new_releases"]:
            from deluge.ui.gtkui.new_release_dialog import NewReleaseDialog
            reactor.callLater(5.0, NewReleaseDialog().show, new_version)

    def on_torrentfinished_event(self, torrent_id):
        from deluge.ui.gtkui.notification import Notification
        Notification().notify(torrent_id)
Пример #15
0
class SystemTray(component.Component):
    def __init__(self):
        component.Component.__init__(self, "SystemTray", interval=4)
        self.window = component.get("MainWindow")
        self.config = ConfigManager("gtkui.conf")
        # List of widgets that need to be hidden when not connected to a host
        self.hide_widget_list = [
            "menuitem_add_torrent",
            "menuitem_pause_all",
            "menuitem_resume_all",
            "menuitem_download_limit",
            "menuitem_upload_limit",
            "menuitem_quitdaemon",
            "separatormenuitem1",
            "separatormenuitem2",
            "separatormenuitem3",
            "separatormenuitem4"
        ]
        self.config.register_set_function("enable_system_tray", self.on_enable_system_tray_set)
        # bit of a hack to prevent function from doing something on startup
        self.__enabled_set_once = False
        self.config.register_set_function("enable_appindicator", self.on_enable_appindicator_set)

        self.max_download_speed = -1.0
        self.download_rate = 0.0
        self.max_upload_speed = -1.0
        self.upload_rate = 0.0

        self.config_value_changed_dict = {
            "max_download_speed": self._on_max_download_speed,
            "max_upload_speed": self._on_max_upload_speed
        }

    def enable(self):
        """Enables the system tray icon."""
        self.builder = gtk.Builder()
        self.builder.add_from_file(deluge.common.resource_filename(
            "deluge.ui.gtkui", os.path.join("glade", "tray_menu.ui"))
        )

        self.builder.connect_signals({
            "on_menuitem_show_deluge_activate": self.on_menuitem_show_deluge_activate,
            "on_menuitem_add_torrent_activate": self.on_menuitem_add_torrent_activate,
            "on_menuitem_pause_all_activate": self.on_menuitem_pause_all_activate,
            "on_menuitem_resume_all_activate": self.on_menuitem_resume_all_activate,
            "on_menuitem_quit_activate": self.on_menuitem_quit_activate,
            "on_menuitem_quitdaemon_activate": self.on_menuitem_quitdaemon_activate
        })

        self.tray_menu = self.builder.get_object("tray_menu")

        if appindicator and self.config["enable_appindicator"]:
            log.debug("Enabling the Application Indicator..")
            self.indicator = appindicator.Indicator (
                "deluge", "deluge", appindicator.CATEGORY_APPLICATION_STATUS)
            # Pass the menu to the Application Indicator
            self.indicator.set_menu(self.tray_menu)

            # Make sure the status of the Show Window MenuItem is correct
            self._sig_win_hide = self.window.window.connect("hide", self._on_window_hide)
            self._sig_win_show = self.window.window.connect("show", self._on_window_show)
            if self.window.visible():
                self.builder.get_object("menuitem_show_deluge").set_active(True)
            else:
                self.builder.get_object("menuitem_show_deluge").set_active(False)

            # Show the Application Indicator
            self.indicator.set_status(appindicator.STATUS_ACTIVE)

        else:
            log.debug("Enabling the system tray icon..")
            if deluge.common.windows_check() or deluge.common.osx_check():
                self.tray = gtk.status_icon_new_from_pixbuf(common.get_logo(32))
            else:
                try:
                    self.tray = gtk.status_icon_new_from_icon_name("deluge")
                except:
                    log.warning("Update PyGTK to 2.10 or greater for SystemTray..")
                    return

            self.tray.connect("activate", self.on_tray_clicked)
            self.tray.connect("popup-menu", self.on_tray_popup)

            # For some reason these icons do not display in appindicator
            self.builder.get_object("download-limit-image").set_from_file(
                deluge.common.get_pixmap("downloading16.png"))
            self.builder.get_object("upload-limit-image").set_from_file(
                deluge.common.get_pixmap("seeding16.png"))

        client.register_event_handler("ConfigValueChangedEvent", self.config_value_changed)
        if client.connected():
            # We're connected so we need to get some values from the core
            self.__start()
        else:
            # Hide menu widgets because we're not connected to a host.
            for widget in self.hide_widget_list:
                self.builder.get_object(widget).hide()

    def __start(self):
        if self.config["enable_system_tray"]:

            if self.config["classic_mode"]:
                self.hide_widget_list.remove("menuitem_quitdaemon")
                self.hide_widget_list.remove("separatormenuitem4")
                self.builder.get_object("menuitem_quitdaemon").hide()
                self.builder.get_object("separatormenuitem4").hide()

            # These do not work with appindicator currently and can crash Deluge.
            # Related to Launchpad bug #608219
            if appindicator and self.config["enable_appindicator"]:
                self.hide_widget_list.remove("menuitem_download_limit")
                self.hide_widget_list.remove("menuitem_upload_limit")
                self.hide_widget_list.remove("separatormenuitem3")
                self.builder.get_object("menuitem_download_limit").hide()
                self.builder.get_object("menuitem_upload_limit").hide()
                self.builder.get_object("separatormenuitem3").hide()

            # Show widgets in the hide list because we've connected to a host
            for widget in self.hide_widget_list:
                self.builder.get_object(widget).show()

            # Build the bandwidth speed limit menus
            self.build_tray_bwsetsubmenu()

            # Get some config values
            client.core.get_config_value(
                "max_download_speed").addCallback(self._on_max_download_speed)
            client.core.get_config_value(
                "max_upload_speed").addCallback(self._on_max_upload_speed)
            self.send_status_request()

    def start(self):
        self.__start()

    def stop(self):
        if self.config["enable_system_tray"] and not self.config["enable_appindicator"]:
            try:
                # Hide widgets in hide list because we're not connected to a host
                for widget in self.hide_widget_list:
                    self.builder.get_object(widget).hide()
            except Exception, e:
                log.debug("Unable to hide system tray menu widgets: %s", e)

            self.tray.set_tooltip(_("Deluge") + "\n" + _("Not Connected..."))
Пример #16
0
class MenuBar(component.Component):
    def __init__(self):
        log.debug("MenuBar init..")
        component.Component.__init__(self, "MenuBar")
        self.window = component.get("MainWindow")
        self.config = ConfigManager("gtkui.conf")

        # Get the torrent menu from the glade file
        self.torrentmenu_glade = gtk.glade.XML(deluge.common.resource_filename(
            "deluge.ui.gtkui", os.path.join("glade", "torrent_menu.glade"))
        )

        self.torrentmenu_glade.get_widget("menuitem_queue").set_submenu(
            self.torrentmenu_glade.get_widget("queue_torrent_menu"))

        # Attach options torrent menu
        self.torrentmenu_glade.get_widget("menuitem_options").set_submenu(
            self.torrentmenu_glade.get_widget("options_torrent_menu"))
        self.torrentmenu_glade.get_widget("download-limit-image").set_from_file(
            deluge.common.get_pixmap("downloading16.png"))
        self.torrentmenu_glade.get_widget("upload-limit-image").set_from_file(
            deluge.common.get_pixmap("seeding16.png"))

        for menuitem in ("menuitem_down_speed", "menuitem_up_speed",
            "menuitem_max_connections", "menuitem_upload_slots"):
            submenu = gtk.Menu()
            item = gtk.MenuItem(_("Set Unlimited"))
            item.set_name(menuitem)
            item.connect("activate", self.on_menuitem_set_unlimited)
            submenu.append(item)
            item = gtk.MenuItem(_("Other..."))
            item.set_name(menuitem)
            item.connect("activate", self.on_menuitem_set_other)
            submenu.append(item)
            submenu.show_all()
            self.torrentmenu_glade.get_widget(menuitem).set_submenu(submenu)

        submenu = gtk.Menu()
        item = gtk.MenuItem(_("On"))
        item.connect("activate", self.on_menuitem_set_automanaged_on)
        submenu.append(item)
        item = gtk.MenuItem(_("Off"))
        item.connect("activate", self.on_menuitem_set_automanaged_off)
        submenu.append(item)
        submenu.show_all()
        self.torrentmenu_glade.get_widget("menuitem_auto_managed").set_submenu(submenu)

        self.torrentmenu = self.torrentmenu_glade.get_widget("torrent_menu")
        self.menu_torrent = self.window.main_glade.get_widget("menu_torrent")

        # Attach the torrent_menu to the Torrent file menu
        self.menu_torrent.set_submenu(self.torrentmenu)

        # Make sure the view menuitems are showing the correct active state
        self.window.main_glade.get_widget("menuitem_toolbar").set_active(
            self.config["show_toolbar"])
        self.window.main_glade.get_widget("menuitem_sidebar").set_active(
            self.config["show_sidebar"])
        self.window.main_glade.get_widget("menuitem_statusbar").set_active(
            self.config["show_statusbar"])
        self.window.main_glade.get_widget("sidebar_show_zero").set_active(
            self.config["sidebar_show_zero"])
        self.window.main_glade.get_widget("sidebar_show_trackers").set_active(
            self.config["sidebar_show_trackers"])


        ### Connect Signals ###
        self.window.main_glade.signal_autoconnect({
            ## File Menu
            "on_menuitem_addtorrent_activate": \
                                        self.on_menuitem_addtorrent_activate,
            "on_menuitem_createtorrent_activate": \
                                        self.on_menuitem_createtorrent_activate,
            "on_menuitem_quitdaemon_activate": \
                                        self.on_menuitem_quitdaemon_activate,
            "on_menuitem_quit_activate": self.on_menuitem_quit_activate,

            ## Edit Menu
            "on_menuitem_preferences_activate": \
                                        self.on_menuitem_preferences_activate,
            "on_menuitem_connectionmanager_activate": \
                self.on_menuitem_connectionmanager_activate,

            ## View Menu
            "on_menuitem_toolbar_toggled": self.on_menuitem_toolbar_toggled,
            "on_menuitem_sidebar_toggled": self.on_menuitem_sidebar_toggled,
            "on_menuitem_statusbar_toggled": self.on_menuitem_statusbar_toggled,

            ## Help Menu
            "on_menuitem_homepage_activate": self.on_menuitem_homepage_activate,
            "on_menuitem_faq_activate": self.on_menuitem_faq_activate,
            "on_menuitem_community_activate": \
                self.on_menuitem_community_activate,
            "on_menuitem_about_activate": self.on_menuitem_about_activate,
            "on_menuitem_sidebar_zero_toggled":self.on_menuitem_sidebar_zero_toggled,
            "on_menuitem_sidebar_trackers_toggled":self.on_menuitem_sidebar_trackers_toggled
        })

        self.torrentmenu_glade.signal_autoconnect({
            ## Torrent Menu
            "on_menuitem_pause_activate": self.on_menuitem_pause_activate,
            "on_menuitem_resume_activate": self.on_menuitem_resume_activate,
            "on_menuitem_updatetracker_activate": \
                                    self.on_menuitem_updatetracker_activate,
            "on_menuitem_edittrackers_activate": \
                                    self.on_menuitem_edittrackers_activate,
            "on_menuitem_remove_activate": \
                self.on_menuitem_remove_activate,
            "on_menuitem_recheck_activate": self.on_menuitem_recheck_activate,
            "on_menuitem_open_folder_activate": self.on_menuitem_open_folder_activate,
            "on_menuitem_move_activate": self.on_menuitem_move_activate,
            "on_menuitem_queue_top_activate": self.on_menuitem_queue_top_activate,
            "on_menuitem_queue_up_activate": self.on_menuitem_queue_up_activate,
            "on_menuitem_queue_down_activate": self.on_menuitem_queue_down_activate,
            "on_menuitem_queue_bottom_activate": self.on_menuitem_queue_bottom_activate
        })

        self.change_sensitivity = [
            "menuitem_addtorrent"
        ]

        self.config.register_set_function("classic_mode", self._on_classic_mode)

        client.register_event_handler("TorrentStateChangedEvent", self.on_torrentstatechanged_event)
        client.register_event_handler("TorrentResumedEvent", self.on_torrentresumed_event)
        client.register_event_handler("SessionPausedEvent", self.on_sessionpaused_event)
        client.register_event_handler("SessionResumedEvent", self.on_sessionresumed_event)

    def start(self):
        for widget in self.change_sensitivity:
            self.window.main_glade.get_widget(widget).set_sensitive(True)

        # Hide the Open Folder menuitem and separator if not connected to a
        # localhost.
        non_remote_items = [
            "menuitem_open_folder",
            "separator4"
        ]
        if not client.is_localhost():
            for widget in non_remote_items:
                self.torrentmenu_glade.get_widget(widget).hide()
                self.torrentmenu_glade.get_widget(widget).set_no_show_all(True)
        else:
            for widget in non_remote_items:
                self.torrentmenu_glade.get_widget(widget).set_no_show_all(False)

        if not self.config["classic_mode"]:
            self.window.main_glade.get_widget("separatormenuitem").show()
            self.window.main_glade.get_widget("menuitem_quitdaemon").show()

        # Show the Torrent menu because we're connected to a host
        self.menu_torrent.show()

        if client.get_auth_level() == deluge.common.AUTH_LEVEL_ADMIN:
            # Get known accounts to allow changing ownership
            client.core.get_known_accounts().addCallback(
                self._on_known_accounts).addErrback(self._on_known_accounts_fail
            )

    def stop(self):
        log.debug("MenuBar stopping")

        for widget in self.change_sensitivity:
            self.window.main_glade.get_widget(widget).set_sensitive(False)

        # Hide the Torrent menu
        self.menu_torrent.hide()

        self.window.main_glade.get_widget("separatormenuitem").hide()
        self.window.main_glade.get_widget("menuitem_quitdaemon").hide()

    def update_menu(self):
        selected = component.get('TorrentView').get_selected_torrents()
        if not selected or len(selected) == 0:
            # No torrent is selected. Disable the 'Torrents' menu
            self.menu_torrent.set_sensitive(False)
            return

        self.menu_torrent.set_sensitive(True)
        # XXX: Should also update Pause/Resume/Remove menuitems.
        # Any better way than duplicating toolbar.py:update_buttons in here?

    def add_torrentmenu_separator(self):
        sep = gtk.SeparatorMenuItem()
        self.torrentmenu.append(sep)
        sep.show()
        return sep

    ### Callbacks ###
    def on_torrentstatechanged_event(self, torrent_id, state):
        if state == "Paused":
            self.update_menu()

    def on_torrentresumed_event(self, torrent_id):
        self.update_menu()

    def on_sessionpaused_event(self):
        self.update_menu()

    def on_sessionresumed_event(self):
        self.update_menu()

    ## File Menu ##
    def on_menuitem_addtorrent_activate(self, data=None):
        log.debug("on_menuitem_addtorrent_activate")
        component.get("AddTorrentDialog").show()

    def on_menuitem_createtorrent_activate(self, data=None):
        log.debug("on_menuitem_createtorrent_activate")
        from createtorrentdialog import CreateTorrentDialog
        CreateTorrentDialog().show()

    def on_menuitem_quitdaemon_activate(self, data=None):
        log.debug("on_menuitem_quitdaemon_activate")
        self.window.quit(shutdown=True)

    def on_menuitem_quit_activate(self, data=None):
        log.debug("on_menuitem_quit_activate")
        self.window.quit()

    ## Edit Menu ##
    def on_menuitem_preferences_activate(self, data=None):
        log.debug("on_menuitem_preferences_activate")
        component.get("Preferences").show()

    def on_menuitem_connectionmanager_activate(self, data=None):
        log.debug("on_menuitem_connectionmanager_activate")
        component.get("ConnectionManager").show()

    ## Torrent Menu ##
    def on_menuitem_pause_activate(self, data=None):
        log.debug("on_menuitem_pause_activate")
        client.core.pause_torrent(
            component.get("TorrentView").get_selected_torrents())

    def on_menuitem_resume_activate(self, data=None):
        log.debug("on_menuitem_resume_activate")
        client.core.resume_torrent(
            component.get("TorrentView").get_selected_torrents())

    def on_menuitem_updatetracker_activate(self, data=None):
        log.debug("on_menuitem_updatetracker_activate")
        client.core.force_reannounce(
            component.get("TorrentView").get_selected_torrents())

    def on_menuitem_edittrackers_activate(self, data=None):
        log.debug("on_menuitem_edittrackers_activate")
        from edittrackersdialog import EditTrackersDialog
        dialog = EditTrackersDialog(
            component.get("TorrentView").get_selected_torrent(),
            component.get("MainWindow").window)
        dialog.run()

    def on_menuitem_remove_activate(self, data=None):
        log.debug("on_menuitem_remove_activate")
        torrent_ids = component.get("TorrentView").get_selected_torrents()
        if torrent_ids:
            from removetorrentdialog import RemoveTorrentDialog
            RemoveTorrentDialog(torrent_ids).run()

    def on_menuitem_recheck_activate(self, data=None):
        log.debug("on_menuitem_recheck_activate")
        client.core.force_recheck(
            component.get("TorrentView").get_selected_torrents())

    def on_menuitem_open_folder_activate(self, data=None):
        log.debug("on_menuitem_open_folder")
        def _on_torrent_status(status):
            deluge.common.open_file(status["save_path"])
        for torrent_id in component.get("TorrentView").get_selected_torrents():
            component.get("SessionProxy").get_torrent_status(
                torrent_id, ["save_path"]).addCallback(_on_torrent_status)

    def on_menuitem_move_activate(self, data=None):
        log.debug("on_menuitem_move_activate")
        if client.is_localhost():
            from deluge.configmanager import ConfigManager
            config = ConfigManager("gtkui.conf")
            chooser = gtk.FileChooserDialog(
                _("Choose a directory to move files to"),
                component.get("MainWindow").window,
                gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER,
                buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
                         gtk.STOCK_OK, gtk.RESPONSE_OK)
            )
            chooser.set_local_only(True)
            if not deluge.common.windows_check():
                chooser.set_icon(common.get_deluge_icon())
                chooser.set_property("skip-taskbar-hint", True)
            chooser.set_current_folder(config["choose_directory_dialog_path"])
            if chooser.run() == gtk.RESPONSE_OK:
                result = chooser.get_filename()
                config["choose_directory_dialog_path"] = result
                client.core.move_storage(
                    component.get("TorrentView").get_selected_torrents(), result)
            chooser.destroy()
        else:
            component.get("SessionProxy").get_torrent_status(
                component.get("TorrentView").get_selected_torrent(),
                ["save_path"]).addCallback(self.show_move_storage_dialog)

    def show_move_storage_dialog(self, status):
        log.debug("show_move_storage_dialog")
        glade = gtk.glade.XML(deluge.common.resource_filename(
            "deluge.ui.gtkui", os.path.join("glade", "move_storage_dialog.glade")
        ))
        # Keep it referenced:
        #  https://bugzilla.gnome.org/show_bug.cgi?id=546802
        self.move_storage_dialog = glade.get_widget("move_storage_dialog")
        self.move_storage_dialog.set_transient_for(self.window.window)
        self.move_storage_dialog_entry = glade.get_widget("entry_destination")
        self.move_storage_dialog_entry.set_text(status["save_path"])
        def on_dialog_response_event(widget, response_id):

            def on_core_result(result):
                # Delete references
                del self.move_storage_dialog
                del self.move_storage_dialog_entry

            if response_id == gtk.RESPONSE_OK:
                log.debug("Moving torrents to %s",
                          self.move_storage_dialog_entry.get_text())
                path = self.move_storage_dialog_entry.get_text()
                client.core.move_storage(
                    component.get("TorrentView").get_selected_torrents(), path
                ).addCallback(on_core_result)
            self.move_storage_dialog.hide()

        self.move_storage_dialog.connect("response", on_dialog_response_event)
        self.move_storage_dialog.show()

    def on_menuitem_queue_top_activate(self, value):
        log.debug("on_menuitem_queue_top_activate")
        client.core.queue_top(component.get("TorrentView").get_selected_torrents())

    def on_menuitem_queue_up_activate(self, value):
        log.debug("on_menuitem_queue_up_activate")
        client.core.queue_up(component.get("TorrentView").get_selected_torrents())

    def on_menuitem_queue_down_activate(self, value):
        log.debug("on_menuitem_queue_down_activate")
        client.core.queue_down(component.get("TorrentView").get_selected_torrents())

    def on_menuitem_queue_bottom_activate(self, value):
        log.debug("on_menuitem_queue_bottom_activate")
        client.core.queue_bottom(component.get("TorrentView").get_selected_torrents())

    ## View Menu ##
    def on_menuitem_toolbar_toggled(self, value):
        log.debug("on_menuitem_toolbar_toggled")
        component.get("ToolBar").visible(value.get_active())

    def on_menuitem_sidebar_toggled(self, value):
        log.debug("on_menuitem_sidebar_toggled")
        component.get("SideBar").visible(value.get_active())

    def on_menuitem_statusbar_toggled(self, value):
        log.debug("on_menuitem_statusbar_toggled")
        component.get("StatusBar").visible(value.get_active())

    ## Help Menu ##
    def on_menuitem_homepage_activate(self, data=None):
        log.debug("on_menuitem_homepage_activate")
        deluge.common.open_url_in_browser("http://deluge-torrent.org")

    def on_menuitem_faq_activate(self, data=None):
        log.debug("on_menuitem_faq_activate")
        deluge.common.open_url_in_browser("http://dev.deluge-torrent.org/wiki/Faq")

    def on_menuitem_community_activate(self, data=None):
        log.debug("on_menuitem_community_activate")
        deluge.common.open_url_in_browser("http://forum.deluge-torrent.org/")

    def on_menuitem_about_activate(self, data=None):
        log.debug("on_menuitem_about_activate")
        from aboutdialog import AboutDialog
        AboutDialog().run()

    def on_menuitem_set_unlimited(self, widget):
        log.debug("widget.name: %s", widget.name)
        funcs = {
            "menuitem_down_speed": client.core.set_torrent_max_download_speed,
            "menuitem_up_speed": client.core.set_torrent_max_upload_speed,
            "menuitem_max_connections": client.core.set_torrent_max_connections,
            "menuitem_upload_slots": client.core.set_torrent_max_upload_slots
        }
        if widget.name in funcs.keys():
            for torrent in component.get("TorrentView").get_selected_torrents():
                funcs[widget.name](torrent, -1)

    def on_menuitem_set_other(self, widget):
        log.debug("widget.name: %s", widget.name)
        funcs = {
            "menuitem_down_speed": client.core.set_torrent_max_download_speed,
            "menuitem_up_speed": client.core.set_torrent_max_upload_speed,
            "menuitem_max_connections": client.core.set_torrent_max_connections,
            "menuitem_upload_slots": client.core.set_torrent_max_upload_slots
        }
        # widget: (header, type_str, image_stockid, image_filename, default)
        other_dialog_info = {
            "menuitem_down_speed": (
                _("Set Maximum Download Speed"),
                "KiB/s", None, "downloading.svg", -1.0
            ),
            "menuitem_up_speed": (
                _("Set Maximum Upload Speed"),
                "KiB/s", None, "seeding.svg", -1.0
            ),
            "menuitem_max_connections": (
                _("Set Maximum Connections"), "", gtk.STOCK_NETWORK, None, -1
            ),
            "menuitem_upload_slots": (
                _("Set Maximum Upload Slots"),
                "", gtk.STOCK_SORT_ASCENDING, None, -1
            )
        }

        # Show the other dialog
        value = common.show_other_dialog(*other_dialog_info[widget.name])
        if value and widget.name in funcs:
            for torrent in component.get("TorrentView").get_selected_torrents():
                funcs[widget.name](torrent, value)

    def on_menuitem_set_automanaged_on(self, widget):
        for torrent in component.get("TorrentView").get_selected_torrents():
            client.core.set_torrent_auto_managed(torrent, True)

    def on_menuitem_set_automanaged_off(self, widget):
        for torrent in component.get("TorrentView").get_selected_torrents():
            client.core.set_torrent_auto_managed(torrent, False)

    def on_menuitem_sidebar_zero_toggled(self, widget):
        self.config["sidebar_show_zero"] = widget.get_active()
        component.get("FilterTreeView").update()

    def on_menuitem_sidebar_trackers_toggled(self, widget):
        self.config["sidebar_show_trackers"] = widget.get_active()
        component.get("FilterTreeView").update()

    def _on_classic_mode(self, key, value):
        items = [
            "menuitem_quitdaemon",
            "separatormenuitem",
            "menuitem_connectionmanager"
        ]

        if value:
            attr = "hide"
        else:
            attr = "show"

        for item in items:
            getattr(self.window.main_glade.get_widget(item), attr)()

    def _on_known_accounts(self, known_accounts):
        known_accounts_to_log = []
        for account in known_accounts:
            account_to_log = {}
            for key, value in account.copy().iteritems():
                if key == 'password':
                    value = '*' * len(value)
                account_to_log[key] = value
            known_accounts_to_log.append(account_to_log)
        log.debug("_on_known_accounts: %s", known_accounts_to_log)
        if len(known_accounts) <= 1:
            return

        self.torrentmenu_glade.get_widget("menuitem_change_owner").set_visible(True)

        self.change_owner_submenu = gtk.Menu()
        self.change_owner_submenu_items = {}
        maingroup = gtk.RadioMenuItem(None, None)

        self.change_owner_submenu_items[None] = gtk.RadioMenuItem(maingroup)

        for account in known_accounts:
            username = account["username"]
            item = gtk.RadioMenuItem(maingroup, username)
            self.change_owner_submenu_items[username] = item
            self.change_owner_submenu.append(item)
            item.connect("toggled", self._on_change_owner_toggled, username)

        self.change_owner_submenu.show_all()
        self.change_owner_submenu_items[None].set_active(True)
        self.change_owner_submenu_items[None].hide()
        self.torrentmenu_glade.get_widget("menuitem_change_owner").connect(
            "activate", self._on_change_owner_submenu_active
        )
        self.torrentmenu_glade.get_widget("menuitem_change_owner").set_submenu(self.change_owner_submenu)

    def _on_known_accounts_fail(self, reason):
        self.torrentmenu_glade.get_widget("menuitem_change_owner").set_visible(False)

    def _on_change_owner_submenu_active(self, widget):
        log.debug("_on_change_owner_submenu_active")
        selected = component.get("TorrentView").get_selected_torrents()
        if len(selected) > 1:
            self.change_owner_submenu_items[None].set_active(True)
            return

        torrent_owner = component.get("TorrentView").get_torrent_status(selected[0])["owner"]
        for username, item in self.change_owner_submenu_items.iteritems():
            item.set_active(username == torrent_owner)

    def _on_change_owner_toggled(self, widget, username):
        log.debug("_on_change_owner_toggled")
        update_torrents = []
        selected = component.get("TorrentView").get_selected_torrents()
        for torrent_id in selected:
            torrent_status = component.get("TorrentView").get_torrent_status(torrent_id)
            if torrent_status["owner"] != username:
                update_torrents.append(torrent_id)

        if update_torrents:
            log.debug("Setting torrent owner \"%s\" on %s", username, update_torrents)

            def failed_change_owner(failure):
                dialogs.ErrorDialog(
                    _("Ownership Change Error"),
                    _("There was an error while trying changing ownership."),
                    self.window.window, details=failure.value.logable()
                ).run()
            client.core.set_torrents_owner(
                update_torrents, username).addErrback(failed_change_owner)
Пример #17
0
class MenuBar(component.Component):
    def __init__(self):
        log.debug("MenuBar init..")
        component.Component.__init__(self, "MenuBar")
        self.window = component.get("MainWindow")
        self.config = ConfigManager("gtkui.conf")

        # Get the torrent menu from the glade file
        self.torrentmenu_glade = gtk.glade.XML(
            deluge.common.resource_filename(
                "deluge.ui.gtkui", os.path.join("glade",
                                                "torrent_menu.glade")))

        self.torrentmenu_glade.get_widget("menuitem_queue").set_submenu(
            self.torrentmenu_glade.get_widget("queue_torrent_menu"))

        # Attach options torrent menu
        self.torrentmenu_glade.get_widget("menuitem_options").set_submenu(
            self.torrentmenu_glade.get_widget("options_torrent_menu"))
        self.torrentmenu_glade.get_widget(
            "download-limit-image").set_from_file(
                deluge.common.get_pixmap("downloading16.png"))
        self.torrentmenu_glade.get_widget("upload-limit-image").set_from_file(
            deluge.common.get_pixmap("seeding16.png"))

        for menuitem in ("menuitem_down_speed", "menuitem_up_speed",
                         "menuitem_max_connections", "menuitem_upload_slots"):
            submenu = gtk.Menu()
            item = gtk.MenuItem(_("Set Unlimited"))
            item.set_name(menuitem)
            item.connect("activate", self.on_menuitem_set_unlimited)
            submenu.append(item)
            item = gtk.MenuItem(_("Other..."))
            item.set_name(menuitem)
            item.connect("activate", self.on_menuitem_set_other)
            submenu.append(item)
            submenu.show_all()
            self.torrentmenu_glade.get_widget(menuitem).set_submenu(submenu)

        submenu = gtk.Menu()
        item = gtk.MenuItem(_("On"))
        item.connect("activate", self.on_menuitem_set_automanaged_on)
        submenu.append(item)
        item = gtk.MenuItem(_("Off"))
        item.connect("activate", self.on_menuitem_set_automanaged_off)
        submenu.append(item)
        submenu.show_all()
        self.torrentmenu_glade.get_widget("menuitem_auto_managed").set_submenu(
            submenu)

        self.torrentmenu = self.torrentmenu_glade.get_widget("torrent_menu")
        self.menu_torrent = self.window.main_glade.get_widget("menu_torrent")

        # Attach the torrent_menu to the Torrent file menu
        self.menu_torrent.set_submenu(self.torrentmenu)

        # Make sure the view menuitems are showing the correct active state
        self.window.main_glade.get_widget("menuitem_toolbar").set_active(
            self.config["show_toolbar"])
        self.window.main_glade.get_widget("menuitem_sidebar").set_active(
            self.config["show_sidebar"])
        self.window.main_glade.get_widget("menuitem_statusbar").set_active(
            self.config["show_statusbar"])
        self.window.main_glade.get_widget("sidebar_show_zero").set_active(
            self.config["sidebar_show_zero"])
        self.window.main_glade.get_widget("sidebar_show_trackers").set_active(
            self.config["sidebar_show_trackers"])

        ### Connect Signals ###
        self.window.main_glade.signal_autoconnect({
            ## File Menu
            "on_menuitem_addtorrent_activate": \
                                        self.on_menuitem_addtorrent_activate,
            "on_menuitem_createtorrent_activate": \
                                        self.on_menuitem_createtorrent_activate,
            "on_menuitem_quitdaemon_activate": \
                                        self.on_menuitem_quitdaemon_activate,
            "on_menuitem_quit_activate": self.on_menuitem_quit_activate,

            ## Edit Menu
            "on_menuitem_preferences_activate": \
                                        self.on_menuitem_preferences_activate,
            "on_menuitem_connectionmanager_activate": \
                self.on_menuitem_connectionmanager_activate,

            ## View Menu
            "on_menuitem_toolbar_toggled": self.on_menuitem_toolbar_toggled,
            "on_menuitem_sidebar_toggled": self.on_menuitem_sidebar_toggled,
            "on_menuitem_statusbar_toggled": self.on_menuitem_statusbar_toggled,

            ## Help Menu
            "on_menuitem_homepage_activate": self.on_menuitem_homepage_activate,
            "on_menuitem_faq_activate": self.on_menuitem_faq_activate,
            "on_menuitem_community_activate": \
                self.on_menuitem_community_activate,
            "on_menuitem_about_activate": self.on_menuitem_about_activate,
            "on_menuitem_sidebar_zero_toggled":self.on_menuitem_sidebar_zero_toggled,
            "on_menuitem_sidebar_trackers_toggled":self.on_menuitem_sidebar_trackers_toggled
        })

        self.torrentmenu_glade.signal_autoconnect({
            ## Torrent Menu
            "on_menuitem_pause_activate": self.on_menuitem_pause_activate,
            "on_menuitem_resume_activate": self.on_menuitem_resume_activate,
            "on_menuitem_updatetracker_activate": \
                                    self.on_menuitem_updatetracker_activate,
            "on_menuitem_edittrackers_activate": \
                                    self.on_menuitem_edittrackers_activate,
            "on_menuitem_remove_activate": \
                self.on_menuitem_remove_activate,
            "on_menuitem_recheck_activate": self.on_menuitem_recheck_activate,
            "on_menuitem_open_folder_activate": self.on_menuitem_open_folder_activate,
            "on_menuitem_move_activate": self.on_menuitem_move_activate,
            "on_menuitem_queue_top_activate": self.on_menuitem_queue_top_activate,
            "on_menuitem_queue_up_activate": self.on_menuitem_queue_up_activate,
            "on_menuitem_queue_down_activate": self.on_menuitem_queue_down_activate,
            "on_menuitem_queue_bottom_activate": self.on_menuitem_queue_bottom_activate
        })

        self.change_sensitivity = ["menuitem_addtorrent"]

        self.config.register_set_function("classic_mode",
                                          self._on_classic_mode)

        client.register_event_handler("TorrentStateChangedEvent",
                                      self.on_torrentstatechanged_event)
        client.register_event_handler("TorrentResumedEvent",
                                      self.on_torrentresumed_event)
        client.register_event_handler("SessionPausedEvent",
                                      self.on_sessionpaused_event)
        client.register_event_handler("SessionResumedEvent",
                                      self.on_sessionresumed_event)

    def start(self):
        for widget in self.change_sensitivity:
            self.window.main_glade.get_widget(widget).set_sensitive(True)

        # Hide the Open Folder menuitem and separator if not connected to a
        # localhost.
        non_remote_items = ["menuitem_open_folder", "separator4"]
        if not client.is_localhost():
            for widget in non_remote_items:
                self.torrentmenu_glade.get_widget(widget).hide()
                self.torrentmenu_glade.get_widget(widget).set_no_show_all(True)
        else:
            for widget in non_remote_items:
                self.torrentmenu_glade.get_widget(widget).set_no_show_all(
                    False)

        if not self.config["classic_mode"]:
            self.window.main_glade.get_widget("separatormenuitem").show()
            self.window.main_glade.get_widget("menuitem_quitdaemon").show()

        # Show the Torrent menu because we're connected to a host
        self.menu_torrent.show()

        if client.get_auth_level() == deluge.common.AUTH_LEVEL_ADMIN:
            # Get known accounts to allow changing ownership
            client.core.get_known_accounts().addCallback(
                self._on_known_accounts).addErrback(
                    self._on_known_accounts_fail)

    def stop(self):
        log.debug("MenuBar stopping")

        for widget in self.change_sensitivity:
            self.window.main_glade.get_widget(widget).set_sensitive(False)

        # Hide the Torrent menu
        self.menu_torrent.hide()

        self.window.main_glade.get_widget("separatormenuitem").hide()
        self.window.main_glade.get_widget("menuitem_quitdaemon").hide()

    def update_menu(self):
        selected = component.get('TorrentView').get_selected_torrents()
        if not selected or len(selected) == 0:
            # No torrent is selected. Disable the 'Torrents' menu
            self.menu_torrent.set_sensitive(False)
            return

        self.menu_torrent.set_sensitive(True)
        # XXX: Should also update Pause/Resume/Remove menuitems.
        # Any better way than duplicating toolbar.py:update_buttons in here?

    def add_torrentmenu_separator(self):
        sep = gtk.SeparatorMenuItem()
        self.torrentmenu.append(sep)
        sep.show()
        return sep

    ### Callbacks ###
    def on_torrentstatechanged_event(self, torrent_id, state):
        if state == "Paused":
            self.update_menu()

    def on_torrentresumed_event(self, torrent_id):
        self.update_menu()

    def on_sessionpaused_event(self):
        self.update_menu()

    def on_sessionresumed_event(self):
        self.update_menu()

    ## File Menu ##
    def on_menuitem_addtorrent_activate(self, data=None):
        log.debug("on_menuitem_addtorrent_activate")
        component.get("AddTorrentDialog").show()

    def on_menuitem_createtorrent_activate(self, data=None):
        log.debug("on_menuitem_createtorrent_activate")
        from createtorrentdialog import CreateTorrentDialog
        CreateTorrentDialog().show()

    def on_menuitem_quitdaemon_activate(self, data=None):
        log.debug("on_menuitem_quitdaemon_activate")
        self.window.quit(shutdown=True)

    def on_menuitem_quit_activate(self, data=None):
        log.debug("on_menuitem_quit_activate")
        self.window.quit()

    ## Edit Menu ##
    def on_menuitem_preferences_activate(self, data=None):
        log.debug("on_menuitem_preferences_activate")
        component.get("Preferences").show()

    def on_menuitem_connectionmanager_activate(self, data=None):
        log.debug("on_menuitem_connectionmanager_activate")
        component.get("ConnectionManager").show()

    ## Torrent Menu ##
    def on_menuitem_pause_activate(self, data=None):
        log.debug("on_menuitem_pause_activate")
        client.core.pause_torrent(
            component.get("TorrentView").get_selected_torrents())

    def on_menuitem_resume_activate(self, data=None):
        log.debug("on_menuitem_resume_activate")
        client.core.resume_torrent(
            component.get("TorrentView").get_selected_torrents())

    def on_menuitem_updatetracker_activate(self, data=None):
        log.debug("on_menuitem_updatetracker_activate")
        client.core.force_reannounce(
            component.get("TorrentView").get_selected_torrents())

    def on_menuitem_edittrackers_activate(self, data=None):
        log.debug("on_menuitem_edittrackers_activate")
        from edittrackersdialog import EditTrackersDialog
        dialog = EditTrackersDialog(
            component.get("TorrentView").get_selected_torrent(),
            component.get("MainWindow").window)
        dialog.run()

    def on_menuitem_remove_activate(self, data=None):
        log.debug("on_menuitem_remove_activate")
        torrent_ids = component.get("TorrentView").get_selected_torrents()
        if torrent_ids:
            from removetorrentdialog import RemoveTorrentDialog
            RemoveTorrentDialog(torrent_ids).run()

    def on_menuitem_recheck_activate(self, data=None):
        log.debug("on_menuitem_recheck_activate")
        client.core.force_recheck(
            component.get("TorrentView").get_selected_torrents())

    def on_menuitem_open_folder_activate(self, data=None):
        log.debug("on_menuitem_open_folder")

        def _on_torrent_status(status):
            deluge.common.open_file(status["save_path"])

        for torrent_id in component.get("TorrentView").get_selected_torrents():
            component.get("SessionProxy").get_torrent_status(
                torrent_id, ["save_path"]).addCallback(_on_torrent_status)

    def on_menuitem_move_activate(self, data=None):
        log.debug("on_menuitem_move_activate")
        if client.is_localhost():
            from deluge.configmanager import ConfigManager
            config = ConfigManager("gtkui.conf")
            chooser = gtk.FileChooserDialog(
                _("Choose a directory to move files to"),
                component.get("MainWindow").window,
                gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER,
                buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OK,
                         gtk.RESPONSE_OK))
            chooser.set_local_only(True)
            if not deluge.common.windows_check():
                chooser.set_icon(common.get_deluge_icon())
                chooser.set_property("skip-taskbar-hint", True)
            chooser.set_current_folder(config["choose_directory_dialog_path"])
            if chooser.run() == gtk.RESPONSE_OK:
                result = chooser.get_filename()
                config["choose_directory_dialog_path"] = result
                client.core.move_storage(
                    component.get("TorrentView").get_selected_torrents(),
                    result)
            chooser.destroy()
        else:
            component.get("SessionProxy").get_torrent_status(
                component.get("TorrentView").get_selected_torrent(),
                ["save_path"]).addCallback(self.show_move_storage_dialog)

    def show_move_storage_dialog(self, status):
        log.debug("show_move_storage_dialog")
        glade = gtk.glade.XML(
            deluge.common.resource_filename(
                "deluge.ui.gtkui",
                os.path.join("glade", "move_storage_dialog.glade")))
        # Keep it referenced:
        #  https://bugzilla.gnome.org/show_bug.cgi?id=546802
        self.move_storage_dialog = glade.get_widget("move_storage_dialog")
        self.move_storage_dialog.set_transient_for(self.window.window)
        self.move_storage_dialog_entry = glade.get_widget("entry_destination")
        self.move_storage_dialog_entry.set_text(status["save_path"])

        def on_dialog_response_event(widget, response_id):
            def on_core_result(result):
                # Delete references
                del self.move_storage_dialog
                del self.move_storage_dialog_entry

            if response_id == gtk.RESPONSE_OK:
                log.debug("Moving torrents to %s",
                          self.move_storage_dialog_entry.get_text())
                path = self.move_storage_dialog_entry.get_text()
                client.core.move_storage(
                    component.get("TorrentView").get_selected_torrents(),
                    path).addCallback(on_core_result)
            self.move_storage_dialog.hide()

        self.move_storage_dialog.connect("response", on_dialog_response_event)
        self.move_storage_dialog.show()

    def on_menuitem_queue_top_activate(self, value):
        log.debug("on_menuitem_queue_top_activate")
        client.core.queue_top(
            component.get("TorrentView").get_selected_torrents())

    def on_menuitem_queue_up_activate(self, value):
        log.debug("on_menuitem_queue_up_activate")
        client.core.queue_up(
            component.get("TorrentView").get_selected_torrents())

    def on_menuitem_queue_down_activate(self, value):
        log.debug("on_menuitem_queue_down_activate")
        client.core.queue_down(
            component.get("TorrentView").get_selected_torrents())

    def on_menuitem_queue_bottom_activate(self, value):
        log.debug("on_menuitem_queue_bottom_activate")
        client.core.queue_bottom(
            component.get("TorrentView").get_selected_torrents())

    ## View Menu ##
    def on_menuitem_toolbar_toggled(self, value):
        log.debug("on_menuitem_toolbar_toggled")
        component.get("ToolBar").visible(value.get_active())

    def on_menuitem_sidebar_toggled(self, value):
        log.debug("on_menuitem_sidebar_toggled")
        component.get("SideBar").visible(value.get_active())

    def on_menuitem_statusbar_toggled(self, value):
        log.debug("on_menuitem_statusbar_toggled")
        component.get("StatusBar").visible(value.get_active())

    ## Help Menu ##
    def on_menuitem_homepage_activate(self, data=None):
        log.debug("on_menuitem_homepage_activate")
        deluge.common.open_url_in_browser("http://deluge-torrent.org")

    def on_menuitem_faq_activate(self, data=None):
        log.debug("on_menuitem_faq_activate")
        deluge.common.open_url_in_browser(
            "http://dev.deluge-torrent.org/wiki/Faq")

    def on_menuitem_community_activate(self, data=None):
        log.debug("on_menuitem_community_activate")
        deluge.common.open_url_in_browser("http://forum.deluge-torrent.org/")

    def on_menuitem_about_activate(self, data=None):
        log.debug("on_menuitem_about_activate")
        from aboutdialog import AboutDialog
        AboutDialog().run()

    def on_menuitem_set_unlimited(self, widget):
        log.debug("widget.name: %s", widget.name)
        funcs = {
            "menuitem_down_speed": client.core.set_torrent_max_download_speed,
            "menuitem_up_speed": client.core.set_torrent_max_upload_speed,
            "menuitem_max_connections":
            client.core.set_torrent_max_connections,
            "menuitem_upload_slots": client.core.set_torrent_max_upload_slots
        }
        if widget.name in funcs.keys():
            for torrent in component.get(
                    "TorrentView").get_selected_torrents():
                funcs[widget.name](torrent, -1)

    def on_menuitem_set_other(self, widget):
        log.debug("widget.name: %s", widget.name)
        funcs = {
            "menuitem_down_speed": client.core.set_torrent_max_download_speed,
            "menuitem_up_speed": client.core.set_torrent_max_upload_speed,
            "menuitem_max_connections":
            client.core.set_torrent_max_connections,
            "menuitem_upload_slots": client.core.set_torrent_max_upload_slots
        }
        # widget: (header, type_str, image_stockid, image_filename, default)
        other_dialog_info = {
            "menuitem_down_speed": (_("Set Maximum Download Speed"), "KiB/s",
                                    None, "downloading.svg", -1.0),
            "menuitem_up_speed": (_("Set Maximum Upload Speed"), "KiB/s", None,
                                  "seeding.svg", -1.0),
            "menuitem_max_connections":
            (_("Set Maximum Connections"), "", gtk.STOCK_NETWORK, None, -1),
            "menuitem_upload_slots": (_("Set Maximum Upload Slots"), "",
                                      gtk.STOCK_SORT_ASCENDING, None, -1)
        }

        # Show the other dialog
        value = common.show_other_dialog(*other_dialog_info[widget.name])
        if value and widget.name in funcs:
            for torrent in component.get(
                    "TorrentView").get_selected_torrents():
                funcs[widget.name](torrent, value)

    def on_menuitem_set_automanaged_on(self, widget):
        for torrent in component.get("TorrentView").get_selected_torrents():
            client.core.set_torrent_auto_managed(torrent, True)

    def on_menuitem_set_automanaged_off(self, widget):
        for torrent in component.get("TorrentView").get_selected_torrents():
            client.core.set_torrent_auto_managed(torrent, False)

    def on_menuitem_sidebar_zero_toggled(self, widget):
        self.config["sidebar_show_zero"] = widget.get_active()
        component.get("FilterTreeView").update()

    def on_menuitem_sidebar_trackers_toggled(self, widget):
        self.config["sidebar_show_trackers"] = widget.get_active()
        component.get("FilterTreeView").update()

    def _on_classic_mode(self, key, value):
        items = [
            "menuitem_quitdaemon", "separatormenuitem",
            "menuitem_connectionmanager"
        ]

        if value:
            attr = "hide"
        else:
            attr = "show"

        for item in items:
            getattr(self.window.main_glade.get_widget(item), attr)()

    def _on_known_accounts(self, known_accounts):
        known_accounts_to_log = []
        for account in known_accounts:
            account_to_log = {}
            for key, value in account.copy().iteritems():
                if key == 'password':
                    value = '*' * len(value)
                account_to_log[key] = value
            known_accounts_to_log.append(account_to_log)
        log.debug("_on_known_accounts: %s", known_accounts_to_log)
        if len(known_accounts) <= 1:
            return

        self.torrentmenu_glade.get_widget("menuitem_change_owner").set_visible(
            True)

        self.change_owner_submenu = gtk.Menu()
        self.change_owner_submenu_items = {}
        maingroup = gtk.RadioMenuItem(None, None)

        self.change_owner_submenu_items[None] = gtk.RadioMenuItem(maingroup)

        for account in known_accounts:
            username = account["username"]
            item = gtk.RadioMenuItem(maingroup, username)
            self.change_owner_submenu_items[username] = item
            self.change_owner_submenu.append(item)
            item.connect("toggled", self._on_change_owner_toggled, username)

        self.change_owner_submenu.show_all()
        self.change_owner_submenu_items[None].set_active(True)
        self.change_owner_submenu_items[None].hide()
        self.torrentmenu_glade.get_widget("menuitem_change_owner").connect(
            "activate", self._on_change_owner_submenu_active)
        self.torrentmenu_glade.get_widget("menuitem_change_owner").set_submenu(
            self.change_owner_submenu)

    def _on_known_accounts_fail(self, reason):
        self.torrentmenu_glade.get_widget("menuitem_change_owner").set_visible(
            False)

    def _on_change_owner_submenu_active(self, widget):
        log.debug("_on_change_owner_submenu_active")
        selected = component.get("TorrentView").get_selected_torrents()
        if len(selected) > 1:
            self.change_owner_submenu_items[None].set_active(True)
            return

        torrent_owner = component.get("TorrentView").get_torrent_status(
            selected[0])["owner"]
        for username, item in self.change_owner_submenu_items.iteritems():
            item.set_active(username == torrent_owner)

    def _on_change_owner_toggled(self, widget, username):
        log.debug("_on_change_owner_toggled")
        update_torrents = []
        selected = component.get("TorrentView").get_selected_torrents()
        for torrent_id in selected:
            torrent_status = component.get("TorrentView").get_torrent_status(
                torrent_id)
            if torrent_status["owner"] != username:
                update_torrents.append(torrent_id)

        if update_torrents:
            log.debug("Setting torrent owner \"%s\" on %s", username,
                      update_torrents)

            def failed_change_owner(failure):
                dialogs.ErrorDialog(
                    _("Ownership Change Error"),
                    _("There was an error while trying changing ownership."),
                    self.window.window,
                    details=failure.value.logable()).run()

            client.core.set_torrents_owner(
                update_torrents, username).addErrback(failed_change_owner)
Пример #18
0
class StatusTab(Tab):
    def __init__(self):
        super(StatusTab, self).__init__('Status', 'status_tab', 'status_tab_label')

        self.config = ConfigManager('gtkui.conf')

        self.progressbar = self.main_builder.get_object('progressbar')
        self.piecesbar = None

        self.add_tab_widget('summary_availability', fratio, ('distributed_copies',))
        self.add_tab_widget('summary_total_downloaded', ftotal_sized,
                            ('all_time_download', 'total_payload_download'))
        self.add_tab_widget('summary_total_uploaded', ftotal_sized,
                            ('total_uploaded', 'total_payload_upload'))
        self.add_tab_widget('summary_download_speed', fspeed_max,
                            ('download_payload_rate', 'max_download_speed'))
        self.add_tab_widget('summary_upload_speed', fspeed_max,
                            ('upload_payload_rate', 'max_upload_speed'))
        self.add_tab_widget('summary_seeds', fpeer, ('num_seeds', 'total_seeds'))
        self.add_tab_widget('summary_peers', fpeer, ('num_peers', 'total_peers'))
        self.add_tab_widget('summary_eta', ftime_or_dash, ('eta',))
        self.add_tab_widget('summary_share_ratio', fratio, ('ratio',))
        self.add_tab_widget('summary_active_time', ftime_or_dash, ('active_time',))
        self.add_tab_widget('summary_seed_time', ftime_or_dash, ('seeding_time',))
        self.add_tab_widget('summary_seed_rank', fseed_rank_or_dash, ('seed_rank', 'seeding_time'))
        self.add_tab_widget('progressbar', fpcnt, ('progress', 'state', 'message'))
        self.add_tab_widget('summary_last_seen_complete', fdate_or_never, ('last_seen_complete',))
        self.add_tab_widget('summary_last_transfer', ftime_or_dash, ('time_since_transfer',))

        self.config.register_set_function('show_piecesbar', self.on_show_piecesbar_config_changed, apply_now=True)

    def update(self):
        # Get the first selected torrent
        selected = component.get('TorrentView').get_selected_torrent()

        if not selected:
            # No torrent is selected in the torrentview
            self.clear()
            return

        # Get the torrent status
        status_keys = self.status_keys
        if self.config['show_piecesbar']:
            status_keys.extend(['pieces', 'num_pieces'])

        component.get('SessionProxy').get_torrent_status(
            selected, status_keys).addCallback(self._on_get_torrent_status)

    def _on_get_torrent_status(self, status):
        # Check to see if we got valid data from the core
        if not status:
            return

        # Update all the label widgets
        for widget in self.tab_widgets.values():
            txt = self.widget_status_as_fstr(widget, status)
            if widget[0].get_text() != txt:
                widget[0].set_text(txt)

        # Update progress bar seperately as it's a special case (not a label).
        fraction = status['progress'] / 100

        if self.config['show_piecesbar']:
            if self.piecesbar.get_fraction() != fraction:
                self.piecesbar.set_fraction(fraction)
            if status['state'] != 'Checking' and self.piecesbar.get_pieces() != status['pieces']:
                # Skip pieces assignment if checking torrent.
                self.piecesbar.set_pieces(status['pieces'], status['num_pieces'])
            self.piecesbar.update()
        else:
            if self.progressbar.get_fraction() != fraction:
                self.progressbar.set_fraction(fraction)

    def on_show_piecesbar_config_changed(self, key, show):
        if show:
            self.show_piecesbar()
        else:
            self.hide_piecesbar()

    def show_piecesbar(self):
        if self.piecesbar is None:
            self.piecesbar = PiecesBar()
            self.main_builder.get_object(
                'status_progress_vbox').pack_start(self.piecesbar, False, False, 0)
        self.tab_widgets['piecesbar'] = TabWidget(self.piecesbar, fpcnt, ('progress', 'state', 'message'))
        self.piecesbar.show()
        self.progressbar.hide()

    def hide_piecesbar(self):
        self.progressbar.show()
        if self.piecesbar:
            self.piecesbar.hide()
            self.tab_widgets.pop('piecesbar', None)
            self.piecesbar = None

    def clear(self):
        for widget in self.tab_widgets.values():
            widget[0].set_text('')

        if self.config['show_piecesbar']:
            self.piecesbar.clear()
        else:
            self.progressbar.set_fraction(0)
Пример #19
0
class TorrentManager(component.Component):
    """
    TorrentManager contains a list of torrents in the current libtorrent
    session.  This object is also responsible for saving the state of the
    session for use on restart.
    """

    def __init__(self):
        component.Component.__init__(self, "TorrentManager", interval=5, depend=["CorePluginManager"])
        log.debug("TorrentManager init..")
        # Set the libtorrent session
        self.session = component.get("Core").session
        # Set the alertmanager
        self.alerts = component.get("AlertManager")
        # Get the core config
        self.config = ConfigManager("core.conf")

        # Make sure the state folder has been created
        if not os.path.exists(os.path.join(get_config_dir(), "state")):
            os.makedirs(os.path.join(get_config_dir(), "state"))

        # Create the torrents dict { torrent_id: Torrent }
        self.torrents = {}
        self.queued_torrents = set()

        # This is a list of torrent_id when we shutdown the torrentmanager.
        # We use this list to determine if all active torrents have been paused
        # and that their resume data has been written.
        self.shutdown_torrent_pause_list = []

        # self.num_resume_data used to save resume_data in bulk
        self.num_resume_data = 0

        # Keep track of torrents finished but moving storage
        self.waiting_on_finish_moving = []

        # Keeps track of resume data that needs to be saved to disk
        self.resume_data = {}

        # Workaround to determine if TorrentAddedEvent is from state file
        self.session_started = False

        # Register set functions
        self.config.register_set_function("max_connections_per_torrent",
            self.on_set_max_connections_per_torrent)
        self.config.register_set_function("max_upload_slots_per_torrent",
            self.on_set_max_upload_slots_per_torrent)
        self.config.register_set_function("max_upload_speed_per_torrent",
            self.on_set_max_upload_speed_per_torrent)
        self.config.register_set_function("max_download_speed_per_torrent",
            self.on_set_max_download_speed_per_torrent)

        # Register alert functions
        self.alerts.register_handler("torrent_finished_alert",
            self.on_alert_torrent_finished)
        self.alerts.register_handler("torrent_paused_alert",
            self.on_alert_torrent_paused)
        self.alerts.register_handler("torrent_checked_alert",
            self.on_alert_torrent_checked)
        self.alerts.register_handler("tracker_reply_alert",
            self.on_alert_tracker_reply)
        self.alerts.register_handler("tracker_announce_alert",
            self.on_alert_tracker_announce)
        self.alerts.register_handler("tracker_warning_alert",
            self.on_alert_tracker_warning)
        self.alerts.register_handler("tracker_error_alert",
            self.on_alert_tracker_error)
        self.alerts.register_handler("storage_moved_alert",
            self.on_alert_storage_moved)
        self.alerts.register_handler("storage_moved_failed_alert",
            self.on_alert_storage_moved_failed)
        self.alerts.register_handler("torrent_resumed_alert",
            self.on_alert_torrent_resumed)
        self.alerts.register_handler("state_changed_alert",
            self.on_alert_state_changed)
        self.alerts.register_handler("save_resume_data_alert",
            self.on_alert_save_resume_data)
        self.alerts.register_handler("save_resume_data_failed_alert",
            self.on_alert_save_resume_data_failed)
        self.alerts.register_handler("file_renamed_alert",
            self.on_alert_file_renamed)
        self.alerts.register_handler("metadata_received_alert",
            self.on_alert_metadata_received)
        self.alerts.register_handler("file_error_alert",
            self.on_alert_file_error)
        self.alerts.register_handler("file_completed_alert",
            self.on_alert_file_completed)

    def start(self):
        # Get the pluginmanager reference
        self.plugins = component.get("CorePluginManager")

        # Run the old state upgrader before loading state
        deluge.core.oldstateupgrader.OldStateUpgrader()

        # Try to load the state from file
        self.load_state()

        # Save the state every 5 minutes
        self.save_state_timer = LoopingCall(self.save_state)
        self.save_state_timer.start(200, False)
        self.save_resume_data_timer = LoopingCall(self.save_resume_data)
        self.save_resume_data_timer.start(190)

    def stop(self):
        # Stop timers
        if self.save_state_timer.running:
            self.save_state_timer.stop()

        if self.save_resume_data_timer.running:
            self.save_resume_data_timer.stop()

        # Save state on shutdown
        self.save_state()

        # Make another list just to make sure all paused torrents will be
        # passed to self.save_resume_data(). With
        # self.shutdown_torrent_pause_list it is possible to have a case when
        # torrent_id is removed from it in self.on_alert_torrent_paused()
        # before we call self.save_resume_data() here.
        save_resume_data_list = []
        for key in self.torrents:
            # Stop the status cleanup LoopingCall here
            self.torrents[key].prev_status_cleanup_loop.stop()
            if not self.torrents[key].handle.is_paused():
                # We set auto_managed false to prevent lt from resuming the torrent
                self.torrents[key].handle.auto_managed(False)
                self.torrents[key].handle.pause()
                self.shutdown_torrent_pause_list.append(key)
                save_resume_data_list.append(key)

        self.save_resume_data(save_resume_data_list)

        # We have to wait for all torrents to pause and write their resume data
        wait = True
        while wait:
            if self.shutdown_torrent_pause_list:
                wait = True
            else:
                wait = False
                for torrent in self.torrents.values():
                    if torrent.waiting_on_resume_data:
                        wait = True
                        break

            time.sleep(0.01)
            # Wait for all alerts
            self.alerts.handle_alerts(True)

    def update(self):
        for torrent_id, torrent in self.torrents.items():
            if torrent.options["stop_at_ratio"] and torrent.state not in ("Checking", "Allocating", "Paused", "Queued"):
                # If the global setting is set, but the per-torrent isn't.. Just skip to the next torrent
                # This is so that a user can turn-off the stop at ratio option on a per-torrent basis
                if not torrent.options["stop_at_ratio"]:
                    continue
                if torrent.get_ratio() >= torrent.options["stop_ratio"] and torrent.is_finished:
                    if torrent.options["remove_at_ratio"]:
                        self.remove(torrent_id)
                        break
                    if not torrent.handle.is_paused():
                        torrent.pause()

    def __getitem__(self, torrent_id):
        """Return the Torrent with torrent_id"""
        return self.torrents[torrent_id]

    def get_torrent_list(self):
        """Returns a list of torrent_ids"""
        return self.torrents.keys()

    def get_torrent_info_from_file(self, filepath):
        """Returns a torrent_info for the file specified or None"""
        torrent_info = None
        # Get the torrent data from the torrent file
        try:
            log.debug("Attempting to create torrent_info from %s", filepath)
            _file = open(filepath, "rb")
            torrent_info = lt.torrent_info(lt.bdecode(_file.read()))
            _file.close()
        except (IOError, RuntimeError), e:
            log.warning("Unable to open %s: %s", filepath, e)

        return torrent_info
Пример #20
0
class MainWindow(component.Component):
    def __init__(self):
        component.Component.__init__(self, "MainWindow", interval=2)
        self.config = ConfigManager("gtkui.conf")
        # Get the glade file for the main window
        self.main_glade = gtk.glade.XML(deluge.common.resource_filename(
            "deluge.ui.gtkui", os.path.join("glade", "main_window.glade"))
        )

        self.window = self.main_glade.get_widget("main_window")

        self.window.set_icon(common.get_deluge_icon())

        self.vpaned = self.main_glade.get_widget("vpaned")
        self.initial_vpaned_position = self.config["window_pane_position"]

        # Load the window state
        self.load_window_state()

        # Keep track of window's minimization state so that we don't update the
        # UI when it is minimized.
        self.is_minimized = False

        self.window.drag_dest_set(gtk.DEST_DEFAULT_ALL, [('text/uri-list', 0,
            80)], gtk.gdk.ACTION_COPY)

        # Connect events
        self.window.connect("window-state-event", self.on_window_state_event)
        self.window.connect("configure-event", self.on_window_configure_event)
        self.window.connect("delete-event", self.on_window_delete_event)
        self.window.connect("drag-data-received", self.on_drag_data_received_event)
        self.vpaned.connect("notify::position", self.on_vpaned_position_event)
        self.window.connect("expose-event", self.on_expose_event)

        self.config.register_set_function("show_rate_in_title", self._on_set_show_rate_in_title, apply_now=False)

        client.register_event_handler("NewVersionAvailableEvent", self.on_newversionavailable_event)
        client.register_event_handler("TorrentFinishedEvent", self.on_torrentfinished_event)

    def first_show(self):
        if not(self.config["start_in_tray"] and \
               self.config["enable_system_tray"]) and not \
                self.window.get_property("visible"):
            log.debug("Showing window")
            self.show()
            while gtk.events_pending():
                gtk.main_iteration(False)
            self.vpaned.set_position(self.initial_vpaned_position)

    def show(self):
        try:
            component.resume("TorrentView")
            component.resume("StatusBar")
            component.resume("TorrentDetails")
        except:
            pass

        self.window.show()


    def hide(self):
        component.pause("TorrentView")
        component.get("TorrentView").save_state()
        component.pause("StatusBar")
        component.pause("TorrentDetails")
        # Store the x, y positions for when we restore the window
        self.window_x_pos = self.window.get_position()[0]
        self.window_y_pos = self.window.get_position()[1]
        self.window.hide()

    def present(self):
        # Restore the proper x,y coords for the window prior to showing it
        try:
            self.config["window_x_pos"] = self.window_x_pos
            self.config["window_y_pos"] = self.window_y_pos
        except:
            pass
        try:
            component.resume("TorrentView")
            component.resume("StatusBar")
            component.resume("TorrentDetails")
        except:
            pass

        self.window.present()
        self.load_window_state()

    def active(self):
        """Returns True if the window is active, False if not."""
        return self.window.is_active()

    def visible(self):
        """Returns True if window is visible, False if not."""
        return self.window.get_property("visible")

    def get_glade(self):
        """Returns a reference to the main window glade object."""
        return self.main_glade

    def quit(self, shutdown=False):
        """
        Quits the GtkUI

        :param shutdown: whether or not to shutdown the daemon as well
        :type shutdown: boolean
        """
        if shutdown:
            def on_daemon_shutdown(result):
                reactor.stop()
            client.daemon.shutdown().addCallback(on_daemon_shutdown)
            return
        if client.is_classicmode():
            reactor.stop()
            return
        if not client.connected():
            reactor.stop()
            return
        def on_client_disconnected(result):
            reactor.stop()
        client.disconnect().addCallback(on_client_disconnected)


    def load_window_state(self):
        x = self.config["window_x_pos"]
        y = self.config["window_y_pos"]
        w = self.config["window_width"]
        h = self.config["window_height"]
        self.window.move(x, y)
        self.window.resize(w, h)
        if self.config["window_maximized"]:
            self.window.maximize()

    def on_window_configure_event(self, widget, event):
        if not self.config["window_maximized"] and self.visible:
            self.config["window_x_pos"] = self.window.get_position()[0]
            self.config["window_y_pos"] = self.window.get_position()[1]
            self.config["window_width"] = event.width
            self.config["window_height"] = event.height

    def on_window_state_event(self, widget, event):
        if event.changed_mask & gtk.gdk.WINDOW_STATE_MAXIMIZED:
            if event.new_window_state & gtk.gdk.WINDOW_STATE_MAXIMIZED:
                log.debug("pos: %s", self.window.get_position())
                self.config["window_maximized"] = True
            else:
                self.config["window_maximized"] = False
        if event.changed_mask & gtk.gdk.WINDOW_STATE_ICONIFIED:
            if event.new_window_state & gtk.gdk.WINDOW_STATE_ICONIFIED:
                log.debug("MainWindow is minimized..")
                component.pause("TorrentView")
                component.pause("StatusBar")
                self.is_minimized = True
            else:
                log.debug("MainWindow is not minimized..")
                try:
                    component.resume("TorrentView")
                    component.resume("StatusBar")
                except:
                    pass
                self.is_minimized = False
        return False

    def on_window_delete_event(self, widget, event):
        if self.config["close_to_tray"] and self.config["enable_system_tray"]:
            self.hide()
        else:
            self.quit()

        return True

    def on_vpaned_position_event(self, obj, param):
        self.config["window_pane_position"] = self.vpaned.get_position()

    def on_drag_data_received_event(self, widget, drag_context, x, y, selection_data, info, timestamp):
        args = []
        for uri in selection_data.data.split():
            if deluge.common.windows_check():
                args.append(urllib.url2pathname(uri[7:]))
            else:
                args.append(urllib.unquote(urlparse(uri).path))
        process_args(args)
        drag_context.finish(True, True)

    def on_expose_event(self, widget, event):
        component.get("SystemTray").blink(False)

    def stop(self):
        self.window.set_title("Deluge")

    def update(self):
        # Update the window title
        def _on_get_session_status(status):
            download_rate = deluge.common.fspeed(status["download_rate"])
            upload_rate = deluge.common.fspeed(status["upload_rate"])
            self.window.set_title("Deluge - %s %s %s %s" % (_("Down:"), download_rate, _("Up:"), upload_rate))
        if self.config["show_rate_in_title"]:
            client.core.get_session_status(["download_rate", "upload_rate"]).addCallback(_on_get_session_status)

    def _on_set_show_rate_in_title(self, key, value):
        if value:
            self.update()
        else:
            self.window.set_title("Deluge")

    def on_newversionavailable_event(self, new_version):
        if self.config["show_new_releases"]:
            from deluge.ui.gtkui.new_release_dialog import NewReleaseDialog
            reactor.callLater(5.0, NewReleaseDialog().show, new_version)

    def on_torrentfinished_event(self, torrent_id):
        from deluge.ui.gtkui.notification import Notification
        Notification().notify(torrent_id)
Пример #21
0
class MainWindow(component.Component):
    def __init__(self):
        component.Component.__init__(self, "MainWindow", interval=2)
        self.config = ConfigManager("gtkui.conf")
        self.gtk_builder_signals_holder = _GtkBuilderSignalsHolder()
        self.main_builder = gtk.Builder()
        # Patch this GtkBuilder to avoid connecting signals from elsewhere
        #
        # Think about splitting up the main window gtkbuilder file into the necessary parts
        # in order not to have to monkey patch GtkBuilder. Those parts would then need to
        # be added to the main window "by hand".
        self.main_builder.prev_connect_signals = copy.deepcopy(self.main_builder.connect_signals)

        def patched_connect_signals(*a, **k):
            raise RuntimeError(
                "In order to connect signals to this GtkBuilder instance please use "
                "'component.get(\"MainWindow\").connect_signals()'"
            )

        self.main_builder.connect_signals = patched_connect_signals

        # Get the gtk builder file for the main window
        self.main_builder.add_from_file(
            deluge.common.resource_filename("deluge.ui.gtkui", os.path.join("glade", "main_window.ui"))
        )
        # The new release dialog
        self.main_builder.add_from_file(
            deluge.common.resource_filename("deluge.ui.gtkui", os.path.join("glade", "main_window.new_release.ui"))
        )
        # The move storage dialog
        self.main_builder.add_from_file(
            deluge.common.resource_filename("deluge.ui.gtkui", os.path.join("glade", "main_window.move_storage.ui"))
        )
        # The tabs
        self.main_builder.add_from_file(
            deluge.common.resource_filename("deluge.ui.gtkui", os.path.join("glade", "main_window.tabs.ui"))
        )
        # The tabs file menu
        self.main_builder.add_from_file(
            deluge.common.resource_filename("deluge.ui.gtkui", os.path.join("glade", "main_window.tabs.menu_file.ui"))
        )
        # The tabs peer menu
        self.main_builder.add_from_file(
            deluge.common.resource_filename("deluge.ui.gtkui", os.path.join("glade", "main_window.tabs.menu_peer.ui"))
        )

        self.window = self.main_builder.get_object("main_window")

        self.window.set_icon(common.get_deluge_icon())

        self.vpaned = self.main_builder.get_object("vpaned")
        self.initial_vpaned_position = self.config["window_pane_position"]

        # Load the window state
        self.load_window_state()

        # Keep track of window's minimization state so that we don't update the
        # UI when it is minimized.
        self.is_minimized = False

        self.window.drag_dest_set(gtk.DEST_DEFAULT_ALL, [("text/uri-list", 0, 80)], gtk.gdk.ACTION_COPY)

        # Connect events
        self.window.connect("window-state-event", self.on_window_state_event)
        self.window.connect("configure-event", self.on_window_configure_event)
        self.window.connect("delete-event", self.on_window_delete_event)
        self.window.connect("drag-data-received", self.on_drag_data_received_event)
        self.vpaned.connect("notify::position", self.on_vpaned_position_event)
        self.window.connect("expose-event", self.on_expose_event)

        self.config.register_set_function("show_rate_in_title", self._on_set_show_rate_in_title, apply_now=False)

        client.register_event_handler("NewVersionAvailableEvent", self.on_newversionavailable_event)
        client.register_event_handler("TorrentFinishedEvent", self.on_torrentfinished_event)

    def connect_signals(self, mapping_or_class):
        self.gtk_builder_signals_holder.connect_signals(mapping_or_class)

    def first_show(self):
        if not (self.config["start_in_tray"] and self.config["enable_system_tray"]) and not self.window.get_property(
            "visible"
        ):
            log.debug("Showing window")
            self.main_builder.prev_connect_signals(self.gtk_builder_signals_holder)
            self.show()
            while gtk.events_pending():
                gtk.main_iteration(False)
            self.vpaned.set_position(self.initial_vpaned_position)

    def show(self):
        try:
            component.resume("TorrentView")
            component.resume("StatusBar")
            component.resume("TorrentDetails")
        except:
            pass

        self.window.show()

    def hide(self):
        component.pause("TorrentView")
        component.get("TorrentView").save_state()
        component.pause("StatusBar")
        component.pause("TorrentDetails")
        # Store the x, y positions for when we restore the window
        self.window_x_pos = self.window.get_position()[0]
        self.window_y_pos = self.window.get_position()[1]
        self.window.hide()

    def present(self):
        # Restore the proper x,y coords for the window prior to showing it
        try:
            self.config["window_x_pos"] = self.window_x_pos
            self.config["window_y_pos"] = self.window_y_pos
        except:
            pass
        try:
            component.resume("TorrentView")
            component.resume("StatusBar")
            component.resume("TorrentDetails")
        except:
            pass

        self.window.present()
        self.load_window_state()

    def active(self):
        """Returns True if the window is active, False if not."""
        return self.window.is_active()

    def visible(self):
        """Returns True if window is visible, False if not."""
        return self.window.get_property("visible")

    def get_builder(self):
        """Returns a reference to the main window GTK builder object."""
        return self.main_builder

    def quit(self, shutdown=False):
        """
        Quits the GtkUI

        :param shutdown: whether or not to shutdown the daemon as well
        :type shutdown: boolean
        """
        if shutdown:

            def on_daemon_shutdown(result):
                try:
                    reactor.stop()
                except ReactorNotRunning:
                    log.debug("Attempted to stop the reactor but it is not running...")

            client.daemon.shutdown().addCallback(on_daemon_shutdown)
            return
        if client.is_classicmode():
            reactor.stop()
            return
        if not client.connected():
            reactor.stop()
            return

        def on_client_disconnected(result):
            reactor.stop()

        client.disconnect().addCallback(on_client_disconnected)

    def load_window_state(self):
        x = self.config["window_x_pos"]
        y = self.config["window_y_pos"]
        w = self.config["window_width"]
        h = self.config["window_height"]
        self.window.move(x, y)
        self.window.resize(w, h)
        if self.config["window_maximized"]:
            self.window.maximize()

    def on_window_configure_event(self, widget, event):
        if not self.config["window_maximized"] and self.visible:
            self.config["window_x_pos"] = self.window.get_position()[0]
            self.config["window_y_pos"] = self.window.get_position()[1]
            self.config["window_width"] = event.width
            self.config["window_height"] = event.height

    def on_window_state_event(self, widget, event):
        if event.changed_mask & gtk.gdk.WINDOW_STATE_MAXIMIZED:
            if event.new_window_state & gtk.gdk.WINDOW_STATE_MAXIMIZED:
                log.debug("pos: %s", self.window.get_position())
                self.config["window_maximized"] = True
            elif not event.new_window_state & gtk.gdk.WINDOW_STATE_WITHDRAWN:
                self.config["window_maximized"] = False
        if event.changed_mask & gtk.gdk.WINDOW_STATE_ICONIFIED:
            if event.new_window_state & gtk.gdk.WINDOW_STATE_ICONIFIED:
                log.debug("MainWindow is minimized..")
                component.pause("TorrentView")
                component.pause("StatusBar")
                self.is_minimized = True
            else:
                log.debug("MainWindow is not minimized..")
                try:
                    component.resume("TorrentView")
                    component.resume("StatusBar")
                except:
                    pass
                self.is_minimized = False
        return False

    def on_window_delete_event(self, widget, event):
        if self.config["close_to_tray"] and self.config["enable_system_tray"]:
            self.hide()
        else:
            self.quit()

        return True

    def on_vpaned_position_event(self, obj, param):
        self.config["window_pane_position"] = self.vpaned.get_position()

    def on_drag_data_received_event(self, widget, drag_context, x, y, selection_data, info, timestamp):
        log.debug("Selection(s) dropped on main window %s", selection_data.data)
        if selection_data.get_uris():
            process_args(selection_data.get_uris())
        else:
            process_args(selection_data.data.split())
        drag_context.finish(True, True)

    def on_expose_event(self, widget, event):
        component.get("SystemTray").blink(False)

    def stop(self):
        self.window.set_title("Deluge")

    def update(self):
        # Update the window title
        def _on_get_session_status(status):
            download_rate = deluge.common.fsize_short(status["payload_download_rate"])
            upload_rate = deluge.common.fsize_short(status["payload_upload_rate"])
            self.window.set_title("%s%s %s%s - Deluge" % (_("D:"), download_rate, _("U:"), upload_rate))

        if self.config["show_rate_in_title"]:
            client.core.get_session_status(["payload_download_rate", "payload_upload_rate"]).addCallback(
                _on_get_session_status
            )

    def _on_set_show_rate_in_title(self, key, value):
        if value:
            self.update()
        else:
            self.window.set_title("Deluge")

    def on_newversionavailable_event(self, new_version):
        if self.config["show_new_releases"]:
            from deluge.ui.gtkui.new_release_dialog import NewReleaseDialog

            reactor.callLater(5.0, NewReleaseDialog().show, new_version)

    def on_torrentfinished_event(self, torrent_id):
        from deluge.ui.gtkui.notification import Notification

        Notification().notify(torrent_id)
Пример #22
0
class MainWindow(component.Component):
    def __init__(self):
        if Wnck:
            self.screen = Wnck.Screen.get_default()
        component.Component.__init__(self, 'MainWindow', interval=2)
        self.config = ConfigManager('gtk3ui.conf')
        self.main_builder = Gtk.Builder()

        # Patch this GtkBuilder to avoid connecting signals from elsewhere
        #
        # Think about splitting up  mainwindow gtkbuilder file into the necessary parts
        # to avoid GtkBuilder monkey patch. Those parts would then need adding to mainwindow 'by hand'.
        self.gtk_builder_signals_holder = _GtkBuilderSignalsHolder()
        # FIXME: The deepcopy has been removed: copy.deepcopy(self.main_builder.connect_signals)
        self.main_builder.prev_connect_signals = self.main_builder.connect_signals

        def patched_connect_signals(*a, **k):
            raise RuntimeError(
                'In order to connect signals to this GtkBuilder instance please use '
                '"component.get(\'MainWindow\').connect_signals()"')

        self.main_builder.connect_signals = patched_connect_signals

        # Get Gtk Builder files Main Window, New release dialog, and Tabs.
        ui_filenames = [
            'main_window.ui',
            'main_window.new_release.ui',
            'main_window.tabs.ui',
            'main_window.tabs.menu_file.ui',
            'main_window.tabs.menu_peer.ui',
        ]
        for filename in ui_filenames:
            self.main_builder.add_from_file(
                resource_filename(__package__, os.path.join('glade',
                                                            filename)))

        self.window = self.main_builder.get_object('main_window')
        self.window.set_icon(get_deluge_icon())
        self.tabsbar_pane = self.main_builder.get_object('tabsbar_pane')
        self.tabsbar_torrent_info = self.main_builder.get_object(
            'torrent_info')
        self.sidebar_pane = self.main_builder.get_object('sidebar_pane')

        # Keep a list of components to pause and resume when changing window state.
        self.child_components = ['TorrentView', 'StatusBar', 'TorrentDetails']

        # Load the window state
        self.load_window_state()

        # Keep track of window minimization state so we don't update UI when it is minimized.
        self.is_minimized = False
        self.restart = False

        self.window.drag_dest_set(
            Gtk.DestDefaults.ALL,
            [Gtk.TargetEntry.new(target='text/uri-list', flags=0, info=80)],
            DragAction.COPY,
        )

        # Connect events
        self.window.connect('window-state-event', self.on_window_state_event)
        self.window.connect('configure-event', self.on_window_configure_event)
        self.window.connect('delete-event', self.on_window_delete_event)
        self.window.connect('drag-data-received',
                            self.on_drag_data_received_event)
        self.window.connect('notify::is-active', self.on_focus)
        self.tabsbar_pane.connect('notify::position',
                                  self.on_tabsbar_pane_position_event)
        self.sidebar_pane.connect('notify::position',
                                  self.on_sidebar_pane_position_event)
        self.window.connect('draw', self.on_expose_event)

        self.config.register_set_function('show_rate_in_title',
                                          self._on_set_show_rate_in_title,
                                          apply_now=False)

        client.register_event_handler('NewVersionAvailableEvent',
                                      self.on_newversionavailable_event)

        self.previous_clipboard_text = ''
        self.first_run = True

    def connect_signals(self, mapping_or_class):
        self.gtk_builder_signals_holder.connect_signals(mapping_or_class)

    def first_show(self):
        self.main_builder.prev_connect_signals(self.gtk_builder_signals_holder)
        self.sidebar_pane.set_position(self.config['sidebar_position'])
        self.tabsbar_pane.set_position(self.config['tabsbar_position'])

        if not (self.config['start_in_tray']
                and self.config['enable_system_tray']
                ) and not self.window.get_property('visible'):
            log.debug('Showing window')
            self.show()

        while Gtk.events_pending():
            Gtk.main_iteration()

    def show(self):
        component.resume(self.child_components)
        self.window.show()

    def hide(self):
        component.get('TorrentView').save_state()
        component.pause(self.child_components)
        self.save_position()
        self.window.hide()

    def present(self):
        def restore():
            # Restore the proper x,y coords for the window prior to showing it
            component.resume(self.child_components)
            timestamp = self.get_timestamp()
            if windowing('X11'):
                # Use present with X11 set_user_time since
                # present_with_time is inconsistent.
                self.window.present()
                self.window.get_window().set_user_time(timestamp)
            else:
                self.window.present_with_time(timestamp)
            self.load_window_state()

        if self.config['lock_tray'] and not self.visible():
            dialog = PasswordDialog(_('Enter your password to show Deluge...'))

            def on_dialog_response(response_id):
                if response_id == Gtk.ResponseType.OK:
                    if (self.config['tray_password'] == sha(
                            decode_bytes(
                                dialog.get_password()).encode()).hexdigest()):
                        restore()

            dialog.run().addCallback(on_dialog_response)
        else:
            restore()

    def get_timestamp(self):
        """Returns the timestamp for the windowing server."""
        timestamp = 0
        gdk_window = self.window.get_window()
        if GdkX11 and isinstance(gdk_window, GdkX11.X11Window):
            timestamp = GdkX11.x11_get_server_time(gdk_window)
        return timestamp

    def active(self):
        """Returns True if the window is active, False if not."""
        return self.window.is_active()

    def visible(self):
        """Returns True if window is visible, False if not."""
        return self.window.get_visible()

    def get_builder(self):
        """Returns a reference to the main window GTK builder object."""
        return self.main_builder

    def quit(self, shutdown=False, restart=False):  # noqa: A003 python builtin
        """Quits the GtkUI application.

        Args:
            shutdown (bool): Whether or not to shutdown the daemon as well.
            restart (bool): Whether or not to restart the application after closing.

        """
        def quit_gtkui():
            def stop_gtk_reactor(result=None):
                self.restart = restart
                try:
                    reactor.callLater(0, reactor.fireSystemEvent,
                                      'gtkui_close')
                except ReactorNotRunning:
                    log.debug(
                        'Attempted to stop the reactor but it is not running...'
                    )

            if shutdown:
                client.daemon.shutdown().addCallback(stop_gtk_reactor)
            elif not client.is_standalone() and client.connected():
                client.disconnect().addCallback(stop_gtk_reactor)
            else:
                stop_gtk_reactor()

        if self.config['lock_tray'] and not self.visible():
            dialog = PasswordDialog(_('Enter your password to Quit Deluge...'))

            def on_dialog_response(response_id):
                if response_id == Gtk.ResponseType.OK:
                    if (self.config['tray_password'] == sha(
                            decode_bytes(
                                dialog.get_password()).encode()).hexdigest()):
                        quit_gtkui()

            dialog.run().addCallback(on_dialog_response)
        else:
            quit_gtkui()

    def load_window_state(self):
        if (self.config['window_x_pos'] == -32000
                or self.config['window_x_pos'] == -32000):
            self.config['window_x_pos'] = self.config['window_y_pos'] = 0

        self.window.move(self.config['window_x_pos'],
                         self.config['window_y_pos'])
        self.window.resize(self.config['window_width'],
                           self.config['window_height'])
        if self.config['window_maximized']:
            self.window.maximize()

    def save_position(self):
        self.config['window_maximized'] = self.window.props.is_maximized
        if not self.config['window_maximized'] and self.visible():
            (
                self.config['window_x_pos'],
                self.config['window_y_pos'],
            ) = self.window.get_position()
            (
                self.config['window_width'],
                self.config['window_height'],
            ) = self.window.get_size()

    def on_window_configure_event(self, widget, event):
        self.save_position()

    def on_window_state_event(self, widget, event):
        if event.changed_mask & WindowState.ICONIFIED:
            if event.new_window_state & WindowState.ICONIFIED:
                log.debug('MainWindow is minimized..')
                component.get('TorrentView').save_state()
                component.pause(self.child_components)
                self.is_minimized = True
            else:
                log.debug('MainWindow is not minimized..')
                component.resume(self.child_components)
                self.is_minimized = False
        return False

    def on_window_delete_event(self, widget, event):
        if self.config['close_to_tray'] and self.config['enable_system_tray']:
            self.hide()
        else:
            self.quit()

        return True

    def on_tabsbar_pane_position_event(self, obj, param):
        self.config['tabsbar_position'] = self.tabsbar_pane.get_position()

    def on_sidebar_pane_position_event(self, obj, param):
        self.config['sidebar_position'] = self.sidebar_pane.get_position()

    def on_drag_data_received_event(self, widget, drag_context, x, y,
                                    selection_data, info, timestamp):
        log.debug('Selection(s) dropped on main window %s',
                  selection_data.get_text())
        if selection_data.get_uris():
            process_args(selection_data.get_uris())
        else:
            process_args(selection_data.get_text().split())
        drag_context.finish(True, True, timestamp)

    def on_expose_event(self, widget, event):
        component.get('SystemTray').blink(False)

    def on_focus(self, window, param):
        if window.props.is_active and not self.first_run and self.config[
                'detect_urls']:
            text = get_clipboard_text()
            if text == self.previous_clipboard_text:
                return
            self.previous_clipboard_text = text
            if text and ((is_url(text) and text.endswith('.torrent'))
                         or is_magnet(text)):
                component.get('AddTorrentDialog').show()
                component.get('AddTorrentDialog').on_button_url_clicked(window)
        self.first_run = False

    def stop(self):
        self.window.set_title('Deluge')

    def update(self):
        # Update the window title
        def _on_get_session_status(status):
            download_rate = fspeed(status['payload_download_rate'],
                                   precision=0,
                                   shortform=True)
            upload_rate = fspeed(status['payload_upload_rate'],
                                 precision=0,
                                 shortform=True)
            self.window.set_title(
                _('D: {download_rate} U: {upload_rate} - Deluge').format(
                    download_rate=download_rate, upload_rate=upload_rate))

        if self.config['show_rate_in_title']:
            client.core.get_session_status(
                ['payload_download_rate',
                 'payload_upload_rate']).addCallback(_on_get_session_status)

    def _on_set_show_rate_in_title(self, key, value):
        if value:
            self.update()
        else:
            self.window.set_title(_('Deluge'))

    def on_newversionavailable_event(self, new_version):
        if self.config['show_new_releases']:
            from .new_release_dialog import NewReleaseDialog

            reactor.callLater(5.0, NewReleaseDialog().show, new_version)

    def is_on_active_workspace(self):
        """Determines if MainWindow is on the active workspace.

        Returns:
            bool: True if on active workspace (or wnck module not available), otherwise False.

        """

        if Wnck:
            self.screen.force_update()
            win = Wnck.Window.get(self.window.get_window().get_xid())
            if win:
                active_wksp = win.get_screen().get_active_workspace()
                if active_wksp:
                    return win.is_on_workspace(active_wksp)
                return False
        return True