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)
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
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])
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)
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..."))
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)
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
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)
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])
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
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()
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()
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
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)
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..."))
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)
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)
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)
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
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)
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)
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