def setup_move_completed_path_chooser(self): self.move_completed_hbox = self.builder.get_object( 'hbox_move_completed_chooser') self.move_completed_path_chooser = PathChooser( 'move_completed_paths_list') self.move_completed_hbox.add(self.move_completed_path_chooser) self.move_completed_hbox.show_all()
def setup_download_location_path_chooser(self): self.download_location_hbox = self.builder.get_object( 'hbox_download_location_chooser') self.download_location_path_chooser = PathChooser( 'download_location_paths_list') self.download_location_hbox.add(self.download_location_path_chooser) self.download_location_hbox.show_all()
def __init__(self): super(OptionsTab, self).__init__('Options', 'options_tab', 'options_tab_label') self.prev_torrent_ids = None self.prev_status = None self.inconsistent_keys = [] # Create TabWidget items with widget id, get/set func name, status key. self.add_tab_widget('spin_max_download', 'value', ['max_download_speed']) self.add_tab_widget('spin_max_upload', 'value', ['max_upload_speed']) self.add_tab_widget('spin_max_connections', 'value_as_int', ['max_connections']) self.add_tab_widget( 'spin_max_upload_slots', 'value_as_int', ['max_upload_slots'] ) self.add_tab_widget( 'chk_prioritize_first_last', 'active', ['prioritize_first_last_pieces'] ) self.add_tab_widget( 'chk_sequential_download', 'active', ['sequential_download'] ) self.add_tab_widget('chk_auto_managed', 'active', ['is_auto_managed']) self.add_tab_widget('chk_stop_at_ratio', 'active', ['stop_at_ratio']) self.add_tab_widget('chk_remove_at_ratio', 'active', ['remove_at_ratio']) self.add_tab_widget('spin_stop_ratio', 'value', ['stop_ratio']) self.add_tab_widget('chk_move_completed', 'active', ['move_completed']) self.add_tab_widget('chk_shared', 'active', ['shared']) self.add_tab_widget('summary_owner', 'text', ['owner']) # Connect key press event for spin widgets. for widget_id in self.tab_widgets: if widget_id.startswith('spin_'): self.tab_widgets[widget_id].obj.connect( 'key-press-event', self.on_key_press_event ) self.button_apply = self.main_builder.get_object('button_apply') self.move_completed_path_chooser = PathChooser('move_completed_paths_list') self.move_completed_path_chooser.set_sensitive( self.tab_widgets['chk_move_completed'].obj.get_active() ) self.move_completed_path_chooser.connect( 'text-changed', self.on_path_chooser_text_changed_event ) self.status_keys.append('move_completed_path') self.move_completed_hbox = self.main_builder.get_object( 'hbox_move_completed_path_chooser' ) self.move_completed_hbox.add(self.move_completed_path_chooser) self.move_completed_hbox.show_all() component.get('MainWindow').connect_signals(self)
def show_move_storage_dialog(self, status): log.debug('show_move_storage_dialog') builder = gtk.Builder() builder.add_from_file( deluge.common.resource_filename( 'deluge.ui.gtkui', os.path.join('glade', 'move_storage_dialog.ui') ) ) # Keep it referenced: # https://bugzilla.gnome.org/show_bug.cgi?id=546802 self.move_storage_dialog = builder.get_object('move_storage_dialog') self.move_storage_dialog.set_transient_for(self.mainwindow.window) self.move_storage_dialog_hbox = builder.get_object('hbox_entry') self.move_storage_path_chooser = PathChooser('move_completed_paths_list') self.move_storage_dialog_hbox.add(self.move_storage_path_chooser) self.move_storage_dialog_hbox.show_all() self.move_storage_path_chooser.set_text(status['download_location']) def on_dialog_response_event(widget, response_id): def on_core_result(result): # Delete references self.move_storage_dialog.hide() del self.move_storage_dialog del self.move_storage_dialog_hbox if response_id == gtk.RESPONSE_CANCEL: on_core_result(None) if response_id == gtk.RESPONSE_OK: log.debug( 'Moving torrents to %s', self.move_storage_path_chooser.get_text() ) path = self.move_storage_path_chooser.get_text() client.core.move_storage( component.get('TorrentView').get_selected_torrents(), path ).addCallback(on_core_result) self.move_storage_dialog.connect('response', on_dialog_response_event) self.move_storage_dialog.show()
def __init__(self): super(OptionsTab, self).__init__('Options', 'options_tab', 'options_tab_label') self.prev_torrent_ids = None self.prev_status = None self.inconsistent_keys = [] # Create TabWidget items with widget id, get/set func name, status key. self.add_tab_widget('spin_max_download', 'value', ['max_download_speed']) self.add_tab_widget('spin_max_upload', 'value', ['max_upload_speed']) self.add_tab_widget('spin_max_connections', 'value_as_int', ['max_connections']) self.add_tab_widget('spin_max_upload_slots', 'value_as_int', ['max_upload_slots']) self.add_tab_widget('chk_prioritize_first_last', 'active', ['prioritize_first_last_pieces']) self.add_tab_widget('chk_sequential_download', 'active', ['sequential_download']) self.add_tab_widget('chk_auto_managed', 'active', ['is_auto_managed']) self.add_tab_widget('chk_stop_at_ratio', 'active', ['stop_at_ratio']) self.add_tab_widget('chk_remove_at_ratio', 'active', ['remove_at_ratio']) self.add_tab_widget('spin_stop_ratio', 'value', ['stop_ratio']) self.add_tab_widget('chk_move_completed', 'active', ['move_completed']) self.add_tab_widget('chk_shared', 'active', ['shared']) self.add_tab_widget('summary_owner', 'text', ['owner']) # Connect key press event for spin widgets. for widget_id in self.tab_widgets: if widget_id.startswith('spin_'): self.tab_widgets[widget_id].obj.connect('key-press-event', self.on_key_press_event) self.button_apply = self.main_builder.get_object('button_apply') self.move_completed_path_chooser = PathChooser('move_completed_paths_list') self.move_completed_path_chooser.set_sensitive( self.tab_widgets['chk_move_completed'].obj.get_active()) self.move_completed_path_chooser.connect( 'text-changed', self.on_path_chooser_text_changed_event) self.status_keys.append('move_completed_path') self.move_completed_hbox = self.main_builder.get_object('hbox_move_completed_path_chooser') self.move_completed_hbox.add(self.move_completed_path_chooser) self.move_completed_hbox.show_all() component.get('MainWindow').connect_signals(self)
def show_move_storage_dialog(self, status): log.debug('show_move_storage_dialog') builder = gtk.Builder() builder.add_from_file(deluge.common.resource_filename( 'deluge.ui.gtkui', os.path.join('glade', 'move_storage_dialog.ui') )) # Keep it referenced: # https://bugzilla.gnome.org/show_bug.cgi?id=546802 self.move_storage_dialog = builder.get_object('move_storage_dialog') self.move_storage_dialog.set_transient_for(self.mainwindow.window) self.move_storage_dialog_hbox = builder.get_object('hbox_entry') self.move_storage_path_chooser = PathChooser('move_completed_paths_list') self.move_storage_dialog_hbox.add(self.move_storage_path_chooser) self.move_storage_dialog_hbox.show_all() self.move_storage_path_chooser.set_text(status['download_location']) def on_dialog_response_event(widget, response_id): def on_core_result(result): # Delete references self.move_storage_dialog.hide() del self.move_storage_dialog del self.move_storage_dialog_hbox if response_id == gtk.RESPONSE_CANCEL: on_core_result(None) if response_id == gtk.RESPONSE_OK: log.debug('Moving torrents to %s', self.move_storage_path_chooser.get_text()) path = self.move_storage_path_chooser.get_text() client.core.move_storage( component.get('TorrentView').get_selected_torrents(), path ).addCallback(on_core_result) self.move_storage_dialog.connect('response', on_dialog_response_event) self.move_storage_dialog.show()
class MenuBar(component.Component): def __init__(self): log.debug('MenuBar init..') component.Component.__init__(self, 'MenuBar') self.mainwindow = component.get('MainWindow') self.main_builder = self.mainwindow.get_builder() self.config = ConfigManager('gtkui.conf') self.builder = gtk.Builder() # Get the torrent menu from the gtk builder file self.builder.add_from_file( deluge.common.resource_filename( 'deluge.ui.gtkui', os.path.join('glade', 'torrent_menu.ui') ) ) # Get the torrent options menu from the gtk builder file self.builder.add_from_file( deluge.common.resource_filename( 'deluge.ui.gtkui', os.path.join('glade', 'torrent_menu.options.ui') ) ) # Get the torrent queue menu from the gtk builder file self.builder.add_from_file( deluge.common.resource_filename( 'deluge.ui.gtkui', os.path.join('glade', 'torrent_menu.queue.ui') ) ) # Attach queue torrent menu torrent_queue_menu = self.builder.get_object('queue_torrent_menu') self.builder.get_object('menuitem_queue').set_submenu(torrent_queue_menu) # Attach options torrent menu torrent_options_menu = self.builder.get_object('options_torrent_menu') self.builder.get_object('menuitem_options').set_submenu(torrent_options_menu) 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') ) 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.builder.get_object(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.builder.get_object('menuitem_auto_managed').set_submenu(submenu) submenu = gtk.Menu() item = gtk.MenuItem(_('Disable')) item.connect('activate', self.on_menuitem_set_stop_seed_at_ratio_disable) submenu.append(item) item = gtk.MenuItem(_('Enable...')) item.set_name('menuitem_stop_seed_at_ratio') item.connect('activate', self.on_menuitem_set_other) submenu.append(item) submenu.show_all() self.builder.get_object('menuitem_stop_seed_at_ratio').set_submenu(submenu) self.torrentmenu = self.builder.get_object('torrent_menu') self.menu_torrent = self.main_builder.get_object('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.main_builder.get_object('menuitem_toolbar').set_active( self.config['show_toolbar'] ) self.main_builder.get_object('menuitem_sidebar').set_active( self.config['show_sidebar'] ) self.main_builder.get_object('menuitem_statusbar').set_active( self.config['show_statusbar'] ) self.main_builder.get_object('sidebar_show_zero').set_active( self.config['sidebar_show_zero'] ) self.main_builder.get_object('sidebar_show_trackers').set_active( self.config['sidebar_show_trackers'] ) self.main_builder.get_object('sidebar_show_owners').set_active( self.config['sidebar_show_owners'] ) # Connect main window Signals # self.mainwindow.connect_signals(self) # Connect menubar signals self.builder.connect_signals(self) self.change_sensitivity = ['menuitem_addtorrent'] def start(self): for widget in self.change_sensitivity: self.main_builder.get_object(widget).set_sensitive(True) # Only show open_folder menuitem and separator if connected to a localhost daemon. localhost_items = ['menuitem_open_folder', 'separator4'] if client.is_localhost(): for widget in localhost_items: self.builder.get_object(widget).show() self.builder.get_object(widget).set_no_show_all(False) else: for widget in localhost_items: self.builder.get_object(widget).hide() self.builder.get_object(widget).set_no_show_all(True) self.main_builder.get_object('separatormenuitem').set_visible( not self.config['standalone'] ) self.main_builder.get_object('menuitem_quitdaemon').set_visible( not self.config['standalone'] ) self.main_builder.get_object('menuitem_connectionmanager').set_visible( not self.config['standalone'] ) # 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) 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 stop(self): log.debug('MenuBar stopping') client.deregister_event_handler( 'TorrentStateChangedEvent', self.on_torrentstatechanged_event ) client.deregister_event_handler( 'TorrentResumedEvent', self.on_torrentresumed_event ) client.deregister_event_handler( 'SessionPausedEvent', self.on_sessionpaused_event ) client.deregister_event_handler( 'SessionResumedEvent', self.on_sessionresumed_event ) for widget in self.change_sensitivity: self.main_builder.get_object(widget).set_sensitive(False) # Hide the Torrent menu self.menu_torrent.hide() self.main_builder.get_object('separatormenuitem').hide() self.main_builder.get_object('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 deluge.ui.gtkui.createtorrentdialog import CreateTorrentDialog CreateTorrentDialog().show() def on_menuitem_quitdaemon_activate(self, data=None): log.debug('on_menuitem_quitdaemon_activate') self.mainwindow.quit(shutdown=True) def on_menuitem_quit_activate(self, data=None): log.debug('on_menuitem_quit_activate') self.mainwindow.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 deluge.ui.gtkui.edittrackersdialog import EditTrackersDialog dialog = EditTrackersDialog( component.get('TorrentView').get_selected_torrent(), self.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 deluge.ui.gtkui.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): timestamp = gtk.get_current_event_time() path = os.path.join( status['download_location'], status['files'][0]['path'].split('/')[0] ) deluge.common.show_file(path, timestamp=timestamp) for torrent_id in component.get('TorrentView').get_selected_torrents(): component.get('SessionProxy').get_torrent_status( torrent_id, ['download_location', 'files'] ).addCallback(_on_torrent_status) def on_menuitem_move_activate(self, data=None): log.debug('on_menuitem_move_activate') component.get('SessionProxy').get_torrent_status( component.get('TorrentView').get_selected_torrent(), ['download_location'] ).addCallback(self.show_move_storage_dialog) def show_move_storage_dialog(self, status): log.debug('show_move_storage_dialog') builder = gtk.Builder() builder.add_from_file( deluge.common.resource_filename( 'deluge.ui.gtkui', os.path.join('glade', 'move_storage_dialog.ui') ) ) # Keep it referenced: # https://bugzilla.gnome.org/show_bug.cgi?id=546802 self.move_storage_dialog = builder.get_object('move_storage_dialog') self.move_storage_dialog.set_transient_for(self.mainwindow.window) self.move_storage_dialog_hbox = builder.get_object('hbox_entry') self.move_storage_path_chooser = PathChooser('move_completed_paths_list') self.move_storage_dialog_hbox.add(self.move_storage_path_chooser) self.move_storage_dialog_hbox.show_all() self.move_storage_path_chooser.set_text(status['download_location']) def on_dialog_response_event(widget, response_id): def on_core_result(result): # Delete references self.move_storage_dialog.hide() del self.move_storage_dialog del self.move_storage_dialog_hbox if response_id == gtk.RESPONSE_CANCEL: on_core_result(None) if response_id == gtk.RESPONSE_OK: log.debug( 'Moving torrents to %s', self.move_storage_path_chooser.get_text() ) path = self.move_storage_path_chooser.get_text() client.core.move_storage( component.get('TorrentView').get_selected_torrents(), path ).addCallback(on_core_result) 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 deluge.ui.gtkui.aboutdialog import AboutDialog AboutDialog().run() def on_menuitem_set_unlimited(self, widget): log.debug('widget name: %s', widget.get_name()) funcs = { 'menuitem_down_speed': 'max_download_speed', 'menuitem_up_speed': 'max_upload_speed', 'menuitem_max_connections': 'max_connections', 'menuitem_upload_slots': 'max_upload_slots', } if widget.get_name() in funcs: torrent_ids = component.get('TorrentView').get_selected_torrents() client.core.set_torrent_options(torrent_ids, {funcs[widget.get_name()]: -1}) def on_menuitem_set_other(self, widget): log.debug('widget name: %s', widget.get_name()) status_map = { 'menuitem_down_speed': ['max_download_speed', 'max_download_speed'], 'menuitem_up_speed': ['max_upload_speed', 'max_upload_speed'], 'menuitem_max_connections': ['max_connections', 'max_connections_global'], 'menuitem_upload_slots': ['max_upload_slots', 'max_upload_slots_global'], 'menuitem_stop_seed_at_ratio': ['stop_ratio', 'stop_seed_ratio'], } other_dialog_info = { 'menuitem_down_speed': [ _('Download Speed Limit'), _('Set the maximum download speed'), _('KiB/s'), 'downloading.svg', ], 'menuitem_up_speed': [ _('Upload Speed Limit'), _('Set the maximum upload speed'), _('KiB/s'), 'seeding.svg', ], 'menuitem_max_connections': [ _('Incoming Connections'), _('Set the maximum incoming connections'), '', gtk.STOCK_NETWORK, ], 'menuitem_upload_slots': [ _('Peer Upload Slots'), _('Set the maximum upload slots'), '', gtk.STOCK_SORT_ASCENDING, ], 'menuitem_stop_seed_at_ratio': [ _('Stop Seed At Ratio'), 'Stop torrent seeding at ratio', '', None, ], } core_key = status_map[widget.get_name()][0] core_key_global = status_map[widget.get_name()][1] def _on_torrent_status(status): other_dialog = other_dialog_info[widget.get_name()] # Add the default using status value if status: other_dialog.append(status[core_key_global]) def set_value(value): if value is not None: if value == 0: value += -1 options = {core_key: value} if core_key == 'stop_ratio': options['stop_at_ratio'] = True client.core.set_torrent_options(torrent_ids, options) dialog = OtherDialog(*other_dialog) dialog.run().addCallback(set_value) torrent_ids = component.get('TorrentView').get_selected_torrents() if len(torrent_ids) == 1: core_key_global = core_key d = component.get('SessionProxy').get_torrent_status( torrent_ids[0], [core_key] ) else: d = client.core.get_config_values([core_key_global]) d.addCallback(_on_torrent_status) def on_menuitem_set_automanaged_on(self, widget): client.core.set_torrent_options( component.get('TorrentView').get_selected_torrents(), {'auto_managed': True} ) def on_menuitem_set_automanaged_off(self, widget): client.core.set_torrent_options( component.get('TorrentView').get_selected_torrents(), {'auto_managed': False}, ) def on_menuitem_set_stop_seed_at_ratio_disable(self, widget): client.core.set_torrent_options( component.get('TorrentView').get_selected_torrents(), {'stop_at_ratio': 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_menuitem_sidebar_owners_toggled(self, widget): self.config['sidebar_show_owners'] = widget.get_active() component.get('FilterTreeView').update() 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().items(): 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.builder.get_object('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(group=maingroup) for account in known_accounts: username = account['username'] item = gtk.RadioMenuItem(group=maingroup, label=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.builder.get_object('menuitem_change_owner').connect( 'activate', self._on_change_owner_submenu_active ) self.builder.get_object('menuitem_change_owner').set_submenu( self.change_owner_submenu ) def _on_known_accounts_fail(self, reason): self.builder.get_object('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.items(): 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): ErrorDialog( _('Ownership Change Error'), _('There was an error while trying changing ownership.'), self.mainwindow.window, details=failure.value.logable(), ).run() client.core.set_torrent_options( update_torrents, {'owner': username} ).addErrback(failed_change_owner)
class OptionsTab(Tab): def __init__(self): super(OptionsTab, self).__init__('Options', 'options_tab', 'options_tab_label') self.prev_torrent_ids = None self.prev_status = None self.inconsistent_keys = [] # Create TabWidget items with widget id, get/set func name, status key. self.add_tab_widget('spin_max_download', 'value', ['max_download_speed']) self.add_tab_widget('spin_max_upload', 'value', ['max_upload_speed']) self.add_tab_widget('spin_max_connections', 'value_as_int', ['max_connections']) self.add_tab_widget('spin_max_upload_slots', 'value_as_int', ['max_upload_slots']) self.add_tab_widget('chk_prioritize_first_last', 'active', ['prioritize_first_last_pieces']) self.add_tab_widget('chk_sequential_download', 'active', ['sequential_download']) self.add_tab_widget('chk_auto_managed', 'active', ['is_auto_managed']) self.add_tab_widget('chk_stop_at_ratio', 'active', ['stop_at_ratio']) self.add_tab_widget('chk_remove_at_ratio', 'active', ['remove_at_ratio']) self.add_tab_widget('spin_stop_ratio', 'value', ['stop_ratio']) self.add_tab_widget('chk_move_completed', 'active', ['move_completed']) self.add_tab_widget('chk_shared', 'active', ['shared']) self.add_tab_widget('summary_owner', 'text', ['owner']) # Connect key press event for spin widgets. for widget_id in self.tab_widgets: if widget_id.startswith('spin_'): self.tab_widgets[widget_id].obj.connect( 'key-press-event', self.on_key_press_event) self.button_apply = self.main_builder.get_object('button_apply') self.move_completed_path_chooser = PathChooser( 'move_completed_paths_list') self.move_completed_path_chooser.set_sensitive( self.tab_widgets['chk_move_completed'].obj.get_active()) self.move_completed_path_chooser.connect( 'text-changed', self.on_path_chooser_text_changed_event) self.status_keys.append('move_completed_path') self.move_completed_hbox = self.main_builder.get_object( 'hbox_move_completed_path_chooser') self.move_completed_hbox.add(self.move_completed_path_chooser) self.move_completed_hbox.show_all() component.get('MainWindow').connect_signals(self) def start(self): pass def stop(self): pass def clear(self): self.prev_torrent_ids = None self.prev_status = None self.inconsistent_keys = [] def update(self): torrent_ids = component.get('TorrentView').get_selected_torrents() # Set True if torrent(s) selected in torrentview, else False. self._child_widget.set_sensitive(bool(torrent_ids)) if torrent_ids: if torrent_ids != self.prev_torrent_ids: self.clear() component.get('SessionProxy').get_torrents_status( { 'id': torrent_ids }, self.status_keys).addCallback(self.parse_torrents_statuses) self.prev_torrent_ids = torrent_ids def parse_torrents_statuses(self, statuses): """Finds common status values to all torrrents in statuses. Values which differ are replaced with config values. Args: statuses (dict): A status dict of {torrent_id: {key: value}}. Returns: dict: A single status dict. """ status = {} if len(statuses) == 1: # A single torrent so pop torrent status. status = statuses.popitem()[1] self.button_apply.set_label('_Apply') else: for status_key in self.status_keys: prev_value = None for idx, status in enumerate(statuses.values()): if idx == 0: prev_value = status[status_key] continue elif status[status_key] != prev_value: self.inconsistent_keys.append(status_key) break status[status_key] = prev_value self.button_apply.set_label(_('_Apply to selected')) self.on_get_torrent_status(status) def on_get_torrent_status(self, new_status): # So we don't overwrite the user's unapplied changes we only # want to update values that have been applied in the core. if self.prev_status is None: self.prev_status = dict.fromkeys(new_status, None) if new_status != self.prev_status: for widget in self.tab_widgets.values(): status_key = widget.status_keys[0] status_value = new_status[status_key] if status_value != self.prev_status[status_key]: set_func = 'set_' + widget.func.replace('_as_int', '') getattr(widget.obj, set_func)(status_value) if set_func == 'set_active': widget.obj.set_inconsistent( status_key in self.inconsistent_keys) if new_status['move_completed_path'] != self.prev_status[ 'move_completed_path']: text = new_status['move_completed_path'] self.move_completed_path_chooser.set_text(text, cursor_end=False, default_text=True) # Update sensitivity of widgets. self.tab_widgets['spin_stop_ratio'].obj.set_sensitive( new_status['stop_at_ratio']) self.tab_widgets['chk_remove_at_ratio'].obj.set_sensitive( new_status['stop_at_ratio']) # Ensure apply button sensitivity is set False. self.button_apply.set_sensitive(False) self.prev_status = new_status # === Widget signal handlers === # def on_button_apply_clicked(self, button): options = {} for widget in self.tab_widgets.values(): status_key = widget.status_keys[0] if status_key == 'owner': continue # A label so read-only widget_value = getattr(widget.obj, 'get_' + widget.func)() if widget_value != self.prev_status[status_key] or ( status_key in self.inconsistent_keys and not widget.obj.get_inconsistent()): options[status_key] = widget_value if options.get('move_completed', False): options[ 'move_completed_path'] = self.move_completed_path_chooser.get_text( ) client.core.set_torrent_options(self.prev_torrent_ids, options) self.button_apply.set_sensitive(False) def on_chk_move_completed_toggled(self, widget): self.move_completed_path_chooser.set_sensitive(widget.get_active()) self.on_chk_toggled(widget) def on_chk_stop_at_ratio_toggled(self, widget): self.tab_widgets['spin_stop_ratio'].obj.set_sensitive( widget.get_active()) self.tab_widgets['chk_remove_at_ratio'].obj.set_sensitive( widget.get_active()) self.on_chk_toggled(widget) def on_chk_toggled(self, widget): widget.set_inconsistent(False) self.button_apply.set_sensitive(True) def on_spin_value_changed(self, widget): self.button_apply.set_sensitive(True) def on_key_press_event(self, widget, event): keyname = keyval_name(event.keyval).lstrip('KP_').lower() if keyname.isdigit() or keyname in [ 'period', 'minus', 'delete', 'backspace' ]: self.button_apply.set_sensitive(True) def on_path_chooser_text_changed_event(self, widget, path): self.button_apply.set_sensitive(True)
class OptionsTab(Tab): def __init__(self): super(OptionsTab, self).__init__('Options', 'options_tab', 'options_tab_label') self.prev_torrent_ids = None self.prev_status = None self.inconsistent_keys = [] # Create TabWidget items with widget id, get/set func name, status key. self.add_tab_widget('spin_max_download', 'value', ['max_download_speed']) self.add_tab_widget('spin_max_upload', 'value', ['max_upload_speed']) self.add_tab_widget('spin_max_connections', 'value_as_int', ['max_connections']) self.add_tab_widget('spin_max_upload_slots', 'value_as_int', ['max_upload_slots']) self.add_tab_widget('chk_prioritize_first_last', 'active', ['prioritize_first_last_pieces']) self.add_tab_widget('chk_sequential_download', 'active', ['sequential_download']) self.add_tab_widget('chk_auto_managed', 'active', ['is_auto_managed']) self.add_tab_widget('chk_stop_at_ratio', 'active', ['stop_at_ratio']) self.add_tab_widget('chk_remove_at_ratio', 'active', ['remove_at_ratio']) self.add_tab_widget('spin_stop_ratio', 'value', ['stop_ratio']) self.add_tab_widget('chk_move_completed', 'active', ['move_completed']) self.add_tab_widget('chk_shared', 'active', ['shared']) self.add_tab_widget('summary_owner', 'text', ['owner']) # Connect key press event for spin widgets. for widget_id in self.tab_widgets: if widget_id.startswith('spin_'): self.tab_widgets[widget_id].obj.connect('key-press-event', self.on_key_press_event) self.button_apply = self.main_builder.get_object('button_apply') self.move_completed_path_chooser = PathChooser('move_completed_paths_list') self.move_completed_path_chooser.set_sensitive( self.tab_widgets['chk_move_completed'].obj.get_active()) self.move_completed_path_chooser.connect( 'text-changed', self.on_path_chooser_text_changed_event) self.status_keys.append('move_completed_path') self.move_completed_hbox = self.main_builder.get_object('hbox_move_completed_path_chooser') self.move_completed_hbox.add(self.move_completed_path_chooser) self.move_completed_hbox.show_all() component.get('MainWindow').connect_signals(self) def start(self): pass def stop(self): pass def clear(self): self.prev_torrent_ids = None self.prev_status = None self.inconsistent_keys = [] def update(self): torrent_ids = component.get('TorrentView').get_selected_torrents() # Set True if torrent(s) selected in torrentview, else False. self._child_widget.set_sensitive(bool(torrent_ids)) if torrent_ids: if torrent_ids != self.prev_torrent_ids: self.clear() component.get('SessionProxy').get_torrents_status( {'id': torrent_ids}, self.status_keys ).addCallback(self.parse_torrents_statuses) self.prev_torrent_ids = torrent_ids def parse_torrents_statuses(self, statuses): """Finds common status values to all torrrents in statuses. Values which differ are replaced with config values. Args: statuses (dict): A status dict of {torrent_id: {key: value}}. Returns: dict: A single status dict. """ status = {} if len(statuses) == 1: # A single torrent so pop torrent status. status = statuses.popitem()[1] self.button_apply.set_label('_Apply') else: for status_key in self.status_keys: prev_value = None for idx, status in enumerate(statuses.values()): if idx == 0: prev_value = status[status_key] continue elif status[status_key] != prev_value: self.inconsistent_keys.append(status_key) break status[status_key] = prev_value self.button_apply.set_label(_('_Apply to selected')) self.on_get_torrent_status(status) def on_get_torrent_status(self, new_status): # So we don't overwrite the user's unapplied changes we only # want to update values that have been applied in the core. if self.prev_status is None: self.prev_status = dict.fromkeys(new_status, None) if new_status != self.prev_status: for widget in self.tab_widgets.values(): status_key = widget.status_keys[0] status_value = new_status[status_key] if status_value != self.prev_status[status_key]: set_func = 'set_' + widget.func.replace('_as_int', '') getattr(widget.obj, set_func)(status_value) if set_func == 'set_active': widget.obj.set_inconsistent(status_key in self.inconsistent_keys) if new_status['move_completed_path'] != self.prev_status['move_completed_path']: text = new_status['move_completed_path'] self.move_completed_path_chooser.set_text(text, cursor_end=False, default_text=True) # Update sensitivity of widgets. self.tab_widgets['spin_stop_ratio'].obj.set_sensitive(new_status['stop_at_ratio']) self.tab_widgets['chk_remove_at_ratio'].obj.set_sensitive(new_status['stop_at_ratio']) # Ensure apply button sensitivity is set False. self.button_apply.set_sensitive(False) self.prev_status = new_status # === Widget signal handlers === # def on_button_apply_clicked(self, button): options = {} for widget in self.tab_widgets.values(): status_key = widget.status_keys[0] if status_key == 'owner': continue # A label so read-only widget_value = getattr(widget.obj, 'get_' + widget.func)() if widget_value != self.prev_status[status_key] or ( status_key in self.inconsistent_keys and not widget.obj.get_inconsistent()): options[status_key] = widget_value if options.get('move_completed', False): options['move_completed_path'] = self.move_completed_path_chooser.get_text() client.core.set_torrent_options(self.prev_torrent_ids, options) self.button_apply.set_sensitive(False) def on_chk_move_completed_toggled(self, widget): self.move_completed_path_chooser.set_sensitive(widget.get_active()) self.on_chk_toggled(widget) def on_chk_stop_at_ratio_toggled(self, widget): self.tab_widgets['spin_stop_ratio'].obj.set_sensitive(widget.get_active()) self.tab_widgets['chk_remove_at_ratio'].obj.set_sensitive(widget.get_active()) self.on_chk_toggled(widget) def on_chk_toggled(self, widget): widget.set_inconsistent(False) self.button_apply.set_sensitive(True) def on_spin_value_changed(self, widget): self.button_apply.set_sensitive(True) def on_key_press_event(self, widget, event): keyname = keyval_name(event.keyval).lstrip('KP_').lower() if keyname.isdigit() or keyname in ['period', 'minus', 'delete', 'backspace']: self.button_apply.set_sensitive(True) def on_path_chooser_text_changed_event(self, widget, path): self.button_apply.set_sensitive(True)
class AddTorrentDialog(component.Component): def __init__(self): component.Component.__init__(self, 'AddTorrentDialog') self.builder = gtk.Builder() # The base dialog self.builder.add_from_file( deluge.common.resource_filename( 'deluge.ui.gtkui', os.path.join('glade', 'add_torrent_dialog.ui'))) # The infohash dialog self.builder.add_from_file( deluge.common.resource_filename( 'deluge.ui.gtkui', os.path.join('glade', 'add_torrent_dialog.infohash.ui'), )) # The url dialog self.builder.add_from_file( deluge.common.resource_filename( 'deluge.ui.gtkui', os.path.join('glade', 'add_torrent_dialog.url.ui'))) self.dialog = self.builder.get_object('dialog_add_torrent') self.dialog.connect('delete-event', self._on_delete_event) self.builder.connect_signals(self) # download?, path, filesize, sequence number, inconsistent? self.files_treestore = gtk.TreeStore(bool, str, TYPE_UINT64, TYPE_INT64, bool, str) self.files_treestore.set_sort_column_id(1, gtk.SORT_ASCENDING) # Holds the files info self.files = {} self.infos = {} self.core_config = {} self.options = {} self.previous_selected_torrent = None self.listview_torrents = self.builder.get_object('listview_torrents') self.listview_files = self.builder.get_object('listview_files') self.prefetching_magnets = [] render = gtk.CellRendererText() render.connect('edited', self._on_torrent_name_edit) render.set_property('editable', True) column = gtk.TreeViewColumn(_('Torrent'), render, text=1) self.listview_torrents.append_column(column) render = gtk.CellRendererToggle() render.connect('toggled', self._on_file_toggled) column = gtk.TreeViewColumn(None, render, active=0, inconsistent=4) self.listview_files.append_column(column) column = gtk.TreeViewColumn(_('Filename')) render = gtk.CellRendererPixbuf() column.pack_start(render, False) column.add_attribute(render, 'stock-id', 5) render = gtk.CellRendererText() render.set_property('editable', True) render.connect('edited', self._on_filename_edited) column.pack_start(render, True) column.add_attribute(render, 'text', 1) column.set_expand(True) self.listview_files.append_column(column) render = gtk.CellRendererText() column = gtk.TreeViewColumn(_('Size')) column.pack_start(render, True) column.set_cell_data_func(render, cell_data_size, 2) self.listview_files.append_column(column) self.torrent_liststore = gtk.ListStore(str, str, str) self.listview_torrents.set_model(self.torrent_liststore) self.listview_torrents.set_tooltip_column(2) self.listview_files.set_model(self.files_treestore) self.listview_files.get_selection().set_mode(gtk.SELECTION_MULTIPLE) self.listview_torrents.get_selection().connect( 'changed', self._on_torrent_changed) self.torrent_liststore.connect('row-inserted', self.update_dialog_title_count) self.torrent_liststore.connect('row-deleted', self.update_dialog_title_count) self.setup_move_completed_path_chooser() self.setup_download_location_path_chooser() # Get default config values from the core self.core_keys = [ 'pre_allocate_storage', 'max_connections_per_torrent', 'max_upload_slots_per_torrent', 'max_upload_speed_per_torrent', 'max_download_speed_per_torrent', 'prioritize_first_last_pieces', 'sequential_download', 'add_paused', 'download_location', 'download_location_paths_list', 'move_completed', 'move_completed_path', 'move_completed_paths_list', ] # self.core_keys += self.move_completed_path_chooser.get_config_keys() self.builder.get_object('notebook1').connect('switch-page', self._on_switch_page) def start(self): self.update_core_config() def show(self, focus=False): self.update_core_config(True, focus) def _show(self, focus=False): if component.get('MainWindow').is_on_active_workspace(): self.dialog.set_transient_for(component.get('MainWindow').window) else: self.dialog.set_transient_for(None) self.dialog.present() if focus: self.dialog.window.focus() def hide(self): self.dialog.hide() self.files = {} self.infos = {} self.options = {} self.previous_selected_torrent = None self.torrent_liststore.clear() self.files_treestore.clear() self.prefetching_magnets = [] self.dialog.set_transient_for(component.get('MainWindow').window) def _on_config_values(self, config, show=False, focus=False): self.core_config = config if self.core_config: self.set_default_options() if show: self._show(focus) def update_core_config(self, show=False, focus=False): # Send requests to the core for these config values d = client.core.get_config_values(self.core_keys) d.addCallback(self._on_config_values, show, focus) def _add_torrent_liststore(self, info_hash, name, filename, files, filedata): """Add a torrent to torrent_liststore.""" if info_hash in self.files: return False torrent_row = [info_hash, name, xml_escape(filename)] row_iter = self.torrent_liststore.append(torrent_row) self.files[info_hash] = files self.infos[info_hash] = filedata self.listview_torrents.get_selection().select_iter(row_iter) self.set_default_options() self.save_torrent_options(row_iter) return row_iter def update_dialog_title_count(self, *args): """Update the AddTorrent dialog title with current torrent count.""" self.dialog.set_title( _('Add Torrents (%d)') % len(self.torrent_liststore)) def show_already_added_dialog(self, count): """Show a message about trying to add duplicate torrents.""" log.debug('Tried to add %d duplicate torrents!', count) ErrorDialog( _('Duplicate torrent(s)'), _('You cannot add the same torrent twice.' ' %d torrents were already added.' % count), self.dialog, ).run() def add_from_files(self, filenames): already_added = 0 for filename in filenames: # Get the torrent data from the torrent file try: info = TorrentInfo(filename) except Exception as ex: log.debug('Unable to open torrent file: %s', ex) ErrorDialog(_('Invalid File'), ex, self.dialog).run() continue if not self._add_torrent_liststore(info.info_hash, info.name, filename, info.files, info.filedata): already_added += 1 if already_added: self.show_already_added_dialog(already_added) def _on_uri_metadata(self, result, uri): """Process prefetched metadata to allow file priority selection.""" info_hash, b64_metadata = result log.debug('on_uri_metadata for %s (%s)', uri, info_hash) if info_hash not in self.prefetching_magnets: return if b64_metadata: metadata = b64decode(b64_metadata) info = TorrentInfo(metadata=metadata) self.files[info_hash] = info.files self.infos[info_hash] = info.filedata else: log.info('Unable to fetch metadata for magnet: %s', uri) self.prefetching_magnets.remove(info_hash) self._on_torrent_changed(self.listview_torrents.get_selection()) def prefetch_waiting_message(self, torrent_id, files): """Show magnet files fetching or failed message above files list.""" if torrent_id in self.prefetching_magnets: self.builder.get_object('prefetch_label').set_text( _('Please wait for files...')) self.builder.get_object('prefetch_spinner').show() self.builder.get_object('prefetch_hbox').show() elif not files: self.builder.get_object('prefetch_label').set_text( _('Unable to download files for this magnet')) self.builder.get_object('prefetch_spinner').hide() self.builder.get_object('prefetch_hbox').show() else: self.builder.get_object('prefetch_hbox').hide() def add_from_magnets(self, uris): """Add a list of magnet uris to torrent_liststore.""" already_added = 0 for uri in uris: magnet = deluge.common.get_magnet_info(uri) if not magnet: log.error('Invalid magnet: %s', uri) continue torrent_id = magnet['info_hash'] files = magnet['files_tree'] if not self._add_torrent_liststore(torrent_id, magnet['name'], xml_escape(uri), files, None): already_added += 1 continue if files: continue d = client.core.prefetch_magnet_metadata(uri) d.addCallback(self._on_uri_metadata, uri) self.prefetching_magnets.append(magnet['info_hash']) self.prefetch_waiting_message(torrent_id, None) if already_added: self.show_already_added_dialog(already_added) def _on_torrent_changed(self, treeselection): (model, row) = treeselection.get_selected() if row is None or not model.iter_is_valid(row): self.files_treestore.clear() self.previous_selected_torrent = None return if model[row][0] not in self.files: self.files_treestore.clear() self.previous_selected_torrent = None return # Save the previous torrents options self.save_torrent_options() torrent_id = model.get_value(row, 0) # Update files list files_list = self.files[torrent_id] self.prepare_file_store(files_list) if self.core_config == {}: self.update_core_config() # Update the options frame self.update_torrent_options(torrent_id) # Update magnet prefetch message self.prefetch_waiting_message(torrent_id, files_list) self.previous_selected_torrent = row def _on_torrent_name_edit(self, w, row, new_name): # TODO: Update torrent name pass def _on_switch_page(self, widget, page, page_num): # Save the torrent options when switching notebook pages self.save_torrent_options() def prepare_file_store(self, files): with listview_replace_treestore(self.listview_files): split_files = {} for idx, _file in enumerate(files): self.prepare_file(_file, _file['path'], idx, _file.get('download', True), split_files) self.add_files(None, split_files) self.listview_files.expand_row(b'0', False) def prepare_file(self, _file, file_name, file_num, download, files_storage): first_slash_index = file_name.find(os.path.sep) if first_slash_index == -1: files_storage[file_name] = (file_num, _file, download) else: file_name_chunk = file_name[:first_slash_index + 1] if file_name_chunk not in files_storage: files_storage[file_name_chunk] = {} self.prepare_file( _file, file_name[first_slash_index + 1:], file_num, download, files_storage[file_name_chunk], ) def add_files(self, parent_iter, split_files): ret = 0 for key, value in split_files.items(): if key.endswith(os.path.sep): chunk_iter = self.files_treestore.append( parent_iter, [True, key, 0, -1, False, gtk.STOCK_DIRECTORY]) chunk_size = self.add_files(chunk_iter, value) self.files_treestore.set(chunk_iter, 2, chunk_size) ret += chunk_size else: self.files_treestore.append( parent_iter, [ value[2], key, value[1]['size'], value[0], False, gtk.STOCK_FILE ], ) ret += value[1]['size'] if parent_iter and self.files_treestore.iter_has_child(parent_iter): # Iterate through the children and see what we should label the # folder, download true, download false or inconsistent. itr = self.files_treestore.iter_children(parent_iter) download = [] download_value = False inconsistent = False while itr: download.append(self.files_treestore.get_value(itr, 0)) itr = self.files_treestore.iter_next(itr) if sum(download) == len(download): download_value = True elif sum(download) == 0: download_value = False else: inconsistent = True self.files_treestore.set_value(parent_iter, 0, download_value) self.files_treestore.set_value(parent_iter, 4, inconsistent) return ret def load_path_choosers_data(self): self.move_completed_path_chooser.set_text( self.core_config['move_completed_path'], cursor_end=False, default_text=True) self.download_location_path_chooser.set_text( self.core_config['download_location'], cursor_end=False, default_text=True) self.builder.get_object('chk_move_completed').set_active( self.core_config['move_completed']) def setup_move_completed_path_chooser(self): self.move_completed_hbox = self.builder.get_object( 'hbox_move_completed_chooser') self.move_completed_path_chooser = PathChooser( 'move_completed_paths_list') self.move_completed_hbox.add(self.move_completed_path_chooser) self.move_completed_hbox.show_all() def setup_download_location_path_chooser(self): self.download_location_hbox = self.builder.get_object( 'hbox_download_location_chooser') self.download_location_path_chooser = PathChooser( 'download_location_paths_list') self.download_location_hbox.add(self.download_location_path_chooser) self.download_location_hbox.show_all() def update_torrent_options(self, torrent_id): if torrent_id not in self.options: self.set_default_options() return options = self.options[torrent_id] self.download_location_path_chooser.set_text( options['download_location'], cursor_end=True) self.move_completed_path_chooser.set_text( options['move_completed_path'], cursor_end=True) self.builder.get_object('spin_maxdown').set_value( options['max_download_speed']) self.builder.get_object('spin_maxup').set_value( options['max_upload_speed']) self.builder.get_object('spin_maxconnections').set_value( options['max_connections']) self.builder.get_object('spin_maxupslots').set_value( options['max_upload_slots']) self.builder.get_object('chk_paused').set_active(options['add_paused']) self.builder.get_object('chk_pre_alloc').set_active( options['pre_allocate_storage']) self.builder.get_object('chk_prioritize').set_active( options['prioritize_first_last_pieces']) self.builder.get_object('chk_sequential_download').set_active( options['sequential_download']) self.builder.get_object('chk_move_completed').set_active( options['move_completed']) def save_torrent_options(self, row=None): # Keeps the torrent options dictionary up-to-date with what the user has # selected. if row is None: if self.previous_selected_torrent and self.torrent_liststore.iter_is_valid( self.previous_selected_torrent): row = self.previous_selected_torrent else: return torrent_id = self.torrent_liststore.get_value(row, 0) if torrent_id in self.options: options = self.options[torrent_id] else: options = {} options[ 'download_location'] = self.download_location_path_chooser.get_text( ) options[ 'move_completed_path'] = self.move_completed_path_chooser.get_text( ) options['pre_allocate_storage'] = self.builder.get_object( 'chk_pre_alloc').get_active() options['move_completed'] = self.builder.get_object( 'chk_move_completed').get_active() options['max_download_speed'] = self.builder.get_object( 'spin_maxdown').get_value() options['max_upload_speed'] = self.builder.get_object( 'spin_maxup').get_value() options['max_connections'] = self.builder.get_object( 'spin_maxconnections').get_value_as_int() options['max_upload_slots'] = self.builder.get_object( 'spin_maxupslots').get_value_as_int() options['add_paused'] = self.builder.get_object( 'chk_paused').get_active() options['prioritize_first_last_pieces'] = self.builder.get_object( 'chk_prioritize').get_active() options['sequential_download'] = ( self.builder.get_object('chk_sequential_download').get_active() or False) options['move_completed'] = self.builder.get_object( 'chk_move_completed').get_active() options['seed_mode'] = self.builder.get_object( 'chk_seed_mode').get_active() self.options[torrent_id] = options # Save the file priorities files_priorities = self.build_priorities( self.files_treestore.get_iter_first(), {}) if len(files_priorities) > 0: for i, file_dict in enumerate(self.files[torrent_id]): file_dict['download'] = files_priorities[i] def build_priorities(self, _iter, priorities): while _iter is not None: if self.files_treestore.iter_has_child(_iter): self.build_priorities( self.files_treestore.iter_children(_iter), priorities) elif not self.files_treestore.get_value(_iter, 1).endswith( os.path.sep): priorities[self.files_treestore.get_value( _iter, 3)] = self.files_treestore.get_value(_iter, 0) _iter = self.files_treestore.iter_next(_iter) return priorities def set_default_options(self): if not self.core_config: # update_core_config will call this method again. self.update_core_config() return self.load_path_choosers_data() self.builder.get_object('chk_pre_alloc').set_active( self.core_config['pre_allocate_storage']) self.builder.get_object('spin_maxdown').set_value( self.core_config['max_download_speed_per_torrent']) self.builder.get_object('spin_maxup').set_value( self.core_config['max_upload_speed_per_torrent']) self.builder.get_object('spin_maxconnections').set_value( self.core_config['max_connections_per_torrent']) self.builder.get_object('spin_maxupslots').set_value( self.core_config['max_upload_slots_per_torrent']) self.builder.get_object('chk_paused').set_active( self.core_config['add_paused']) self.builder.get_object('chk_prioritize').set_active( self.core_config['prioritize_first_last_pieces']) self.builder.get_object('chk_sequential_download').set_active( self.core_config['sequential_download']) self.builder.get_object('chk_move_completed').set_active( self.core_config['move_completed']) self.builder.get_object('chk_seed_mode').set_active(False) def get_file_priorities(self, torrent_id): # A list of priorities files_list = [] for file_dict in self.files[torrent_id]: if not file_dict['download']: files_list.append(0) else: # Default lt file priority is 4 files_list.append(4) return files_list def _on_file_toggled(self, render, path): (model, paths) = self.listview_files.get_selection().get_selected_rows() if len(paths) > 1: for path in paths: row = model.get_iter(path) self.toggle_iter(row) else: row = model.get_iter(path) self.toggle_iter(row) self.update_treeview_toggles(self.files_treestore.get_iter_first()) def toggle_iter(self, _iter, toggle_to=None): if toggle_to is None: toggle_to = not self.files_treestore.get_value(_iter, 0) self.files_treestore.set_value(_iter, 0, toggle_to) if self.files_treestore.iter_has_child(_iter): child = self.files_treestore.iter_children(_iter) while child is not None: self.toggle_iter(child, toggle_to) child = self.files_treestore.iter_next(child) def update_treeview_toggles(self, _iter): toggle_inconsistent = -1 this_level_toggle = None while _iter is not None: if self.files_treestore.iter_has_child(_iter): toggle = self.update_treeview_toggles( self.files_treestore.iter_children(_iter)) if toggle == toggle_inconsistent: self.files_treestore.set_value(_iter, 4, True) else: self.files_treestore.set_value(_iter, 0, toggle) # set inconsistent to false self.files_treestore.set_value(_iter, 4, False) else: toggle = self.files_treestore.get_value(_iter, 0) if this_level_toggle is None: this_level_toggle = toggle elif this_level_toggle != toggle: this_level_toggle = toggle_inconsistent _iter = self.files_treestore.iter_next(_iter) return this_level_toggle def on_button_file_clicked(self, widget): log.debug('on_button_file_clicked') # Setup the filechooserdialog chooser = gtk.FileChooserDialog( _('Choose a .torrent file'), None, gtk.FILE_CHOOSER_ACTION_OPEN, buttons=( gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_OK, ), ) chooser.set_transient_for(self.dialog) chooser.set_select_multiple(True) chooser.set_property('skip-taskbar-hint', True) chooser.set_local_only(False) # Add .torrent and * file filters file_filter = gtk.FileFilter() file_filter.set_name(_('Torrent files')) file_filter.add_pattern('*.' + 'torrent') chooser.add_filter(file_filter) file_filter = gtk.FileFilter() file_filter.set_name(_('All files')) file_filter.add_pattern('*') chooser.add_filter(file_filter) # Load the 'default_load_path' from the config self.config = ConfigManager('gtkui.conf') if self.config['default_load_path'] is not None: chooser.set_current_folder(self.config['default_load_path']) # Run the dialog response = chooser.run() if response == gtk.RESPONSE_OK: result = chooser.get_filenames() self.config['default_load_path'] = chooser.get_current_folder() else: chooser.destroy() return chooser.destroy() self.add_from_files(result) def on_button_url_clicked(self, widget): log.debug('on_button_url_clicked') dialog = self.builder.get_object('url_dialog') entry = self.builder.get_object('entry_url') dialog.set_default_response(gtk.RESPONSE_OK) dialog.set_transient_for(self.dialog) entry.grab_focus() text = get_clipboard_text() if text and deluge.common.is_url(text) or deluge.common.is_magnet( text): entry.set_text(text) dialog.show_all() response = dialog.run() if response == gtk.RESPONSE_OK: url = entry.get_text().decode('utf-8') else: url = None entry.set_text('') dialog.hide() # This is where we need to fetch the .torrent file from the URL and # add it to the list. log.debug('url: %s', url) if url: if deluge.common.is_url(url): self.add_from_url(url) elif deluge.common.is_magnet(url): self.add_from_magnets([url]) else: ErrorDialog( _('Invalid URL'), '%s %s' % (url, _('is not a valid URL.')), self.dialog, ).run() def add_from_url(self, url): dialog = gtk.Dialog( _('Downloading...'), flags=gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_NO_SEPARATOR, parent=self.dialog, ) dialog.set_transient_for(self.dialog) pb = gtk.ProgressBar() dialog.vbox.pack_start(pb, True, True, 0) dialog.show_all() # Create a tmp file path import tempfile tmp_fd, tmp_file = tempfile.mkstemp(prefix='deluge_url.', suffix='.torrent') def on_part(data, current_length, total_length): if total_length: percent = current_length / total_length pb.set_fraction(percent) pb.set_text('%.2f%% (%s / %s)' % ( percent * 100, deluge.common.fsize(current_length), deluge.common.fsize(total_length), )) else: pb.pulse() pb.set_text('%s' % deluge.common.fsize(current_length)) def on_download_success(result): self.add_from_files([result]) dialog.destroy() def on_download_fail(result): log.debug('Download failed: %s', result) dialog.destroy() ErrorDialog( _('Download Failed'), '%s %s' % (_('Failed to download:'), url), details=result.getErrorMessage(), parent=self.dialog, ).run() return result d = download_file(url, tmp_file, on_part) os.close(tmp_fd) d.addCallbacks(on_download_success, on_download_fail) def on_button_hash_clicked(self, widget): log.debug('on_button_hash_clicked') dialog = self.builder.get_object('dialog_infohash') entry = self.builder.get_object('entry_hash') textview = self.builder.get_object('text_trackers') dialog.set_default_response(gtk.RESPONSE_OK) dialog.set_transient_for(self.dialog) entry.grab_focus() text = get_clipboard_text() if deluge.common.is_infohash(text): entry.set_text(text) dialog.show_all() response = dialog.run() infohash = entry.get_text().strip() if response == gtk.RESPONSE_OK and deluge.common.is_infohash(infohash): # Create a list of trackers from the textview buffer tview_buf = textview.get_buffer() trackers_text = tview_buf.get_text(*tview_buf.get_bounds()) log.debug('Create torrent tracker lines: %s', trackers_text) trackers = list(trackers_tiers_from_text(trackers_text).keys()) # Convert the information to a magnet uri, this is just easier to # handle this way. log.debug('trackers: %s', trackers) magnet = deluge.common.create_magnet_uri(infohash, infohash, trackers) log.debug('magnet uri: %s', magnet) self.add_from_magnets([magnet]) entry.set_text('') textview.get_buffer().set_text('') dialog.hide() def on_button_remove_clicked(self, widget): log.debug('on_button_remove_clicked') (model, row) = self.listview_torrents.get_selection().get_selected() if row is None: return torrent_id = model.get_value(row, 0) model.remove(row) del self.files[torrent_id] del self.infos[torrent_id] def on_button_trackers_clicked(self, widget): log.debug('on_button_trackers_clicked') def on_button_cancel_clicked(self, widget): log.debug('on_button_cancel_clicked') self.hide() def on_button_add_clicked(self, widget): log.debug('on_button_add_clicked') self.add_torrents() self.hide() def add_torrents(self): (model, row) = self.listview_torrents.get_selection().get_selected() if row is not None: self.save_torrent_options(row) torrents_to_add = [] row = self.torrent_liststore.get_iter_first() while row is not None: torrent_id = self.torrent_liststore.get_value(row, 0) filename = xml_unescape(self.torrent_liststore.get_value(row, 2)) try: options = self.options[torrent_id] except KeyError: options = None file_priorities = self.get_file_priorities(torrent_id) if options is not None: options['file_priorities'] = file_priorities if self.infos[torrent_id]: torrents_to_add.append(( os.path.split(filename)[-1], b64encode(self.infos[torrent_id]), options, )) elif deluge.common.is_magnet(filename): client.core.add_torrent_magnet(filename, options) row = self.torrent_liststore.iter_next(row) def on_torrents_added(errors): if errors: log.info( 'Failed to add %d out of %d torrents.', len(errors), len(torrents_to_add), ) for e in errors: log.info('Torrent add failed: %s', e) else: log.info('Successfully added %d torrents.', len(torrents_to_add)) client.core.add_torrent_files(torrents_to_add).addCallback( on_torrents_added) def on_button_apply_clicked(self, widget): log.debug('on_button_apply_clicked') (model, row) = self.listview_torrents.get_selection().get_selected() if row is None: return self.save_torrent_options(row) # The options, except file renames, we want all the torrents to have options = self.options[model.get_value(row, 0)].copy() options.pop('mapped_files', None) # Set all the torrent options row = model.get_iter_first() while row is not None: torrent_id = model.get_value(row, 0) self.options[torrent_id].update(options) row = model.iter_next(row) def on_button_revert_clicked(self, widget): log.debug('on_button_revert_clicked') (model, row) = self.listview_torrents.get_selection().get_selected() if row is None: return del self.options[model.get_value(row, 0)] self.set_default_options() def on_chk_move_completed_toggled(self, widget): value = widget.get_active() self.move_completed_path_chooser.set_sensitive(value) def _on_delete_event(self, widget, event): self.hide() return True def get_file_path(self, row, path=''): if not row: return path path = self.files_treestore[row][1] + path return self.get_file_path(self.files_treestore.iter_parent(row), path) def _on_filename_edited(self, renderer, path, new_text): index = self.files_treestore[path][3] new_text = new_text.strip(os.path.sep).strip() # Return if the text hasn't changed if new_text == self.files_treestore[path][1]: return # Get the tree iter itr = self.files_treestore.get_iter(path) # Get the torrent_id (model, row) = self.listview_torrents.get_selection().get_selected() torrent_id = model[row][0] if 'mapped_files' not in self.options[torrent_id]: self.options[torrent_id]['mapped_files'] = {} if index > -1: # We're renaming a file! Yay! That's easy! if not new_text: return parent = self.files_treestore.iter_parent(itr) file_path = os.path.join(self.get_file_path(parent), new_text) # Don't rename if filename exists if parent: for row in self.files_treestore[parent].iterchildren(): if new_text == row[1]: return if os.path.sep in new_text: # There are folders in this path, so we need to create them # and then move the file iter to top split_text = new_text.split(os.path.sep) for s in split_text[:-1]: parent = self.files_treestore.append( parent, [True, s, 0, -1, False, gtk.STOCK_DIRECTORY]) self.files_treestore[itr][1] = split_text[-1] reparent_iter(self.files_treestore, itr, parent) else: # Update the row's text self.files_treestore[itr][1] = new_text # Update the mapped_files dict in the options with the index and new # file path. # We'll send this to the core when adding the torrent so it knows # what to rename before adding. self.options[torrent_id]['mapped_files'][index] = file_path self.files[torrent_id][index]['path'] = file_path else: # Folder! def walk_tree(row): if not row: return # Get the file path base once, since it will be the same for # all siblings file_path_base = self.get_file_path( self.files_treestore.iter_parent(row)) # Iterate through all the siblings at this level while row: # We recurse if there are children if self.files_treestore.iter_has_child(row): walk_tree(self.files_treestore.iter_children(row)) index = self.files_treestore[row][3] if index > -1: # Get the new full path for this file file_path = file_path_base + self.files_treestore[row][ 1] # Update the file path in the mapped_files dict self.options[torrent_id]['mapped_files'][ index] = file_path self.files[torrent_id][index]['path'] = file_path # Get the next siblings iter row = self.files_treestore.iter_next(row) # Update the treestore row first so that when walking the tree # we can construct the new proper paths # We need to check if this folder has been split if os.path.sep in new_text: # It's been split, so we need to add new folders and then re-parent # itr. parent = self.files_treestore.iter_parent(itr) split_text = new_text.split(os.path.sep) for s in split_text[:-1]: # We don't iterate over the last item because we'll just use # the existing itr and change the text parent = self.files_treestore.append( parent, [ True, s + os.path.sep, 0, -1, False, gtk.STOCK_DIRECTORY ], ) self.files_treestore[itr][1] = split_text[-1] + os.path.sep # Now re-parent itr to parent reparent_iter(self.files_treestore, itr, parent) itr = parent # We need to re-expand the view because it might contracted # if we change the root iter self.listview_files.expand_row(b'0', False) else: # This was a simple folder rename without any splits, so just # change the path for itr self.files_treestore[itr][1] = new_text + os.path.sep # Walk through the tree from 'itr' and add all the new file paths # to the 'mapped_files' option walk_tree(itr)
class MenuBar(component.Component): def __init__(self): log.debug('MenuBar init..') component.Component.__init__(self, 'MenuBar') self.mainwindow = component.get('MainWindow') self.main_builder = self.mainwindow.get_builder() self.config = ConfigManager('gtkui.conf') self.builder = gtk.Builder() # Get the torrent menu from the gtk builder file self.builder.add_from_file(deluge.common.resource_filename( 'deluge.ui.gtkui', os.path.join('glade', 'torrent_menu.ui') )) # Get the torrent options menu from the gtk builder file self.builder.add_from_file(deluge.common.resource_filename( 'deluge.ui.gtkui', os.path.join('glade', 'torrent_menu.options.ui') )) # Get the torrent queue menu from the gtk builder file self.builder.add_from_file(deluge.common.resource_filename( 'deluge.ui.gtkui', os.path.join('glade', 'torrent_menu.queue.ui') )) # Attach queue torrent menu torrent_queue_menu = self.builder.get_object('queue_torrent_menu') self.builder.get_object('menuitem_queue').set_submenu(torrent_queue_menu) # Attach options torrent menu torrent_options_menu = self.builder.get_object('options_torrent_menu') self.builder.get_object('menuitem_options').set_submenu(torrent_options_menu) 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')) 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.builder.get_object(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.builder.get_object('menuitem_auto_managed').set_submenu(submenu) submenu = gtk.Menu() item = gtk.MenuItem(_('Disable')) item.connect('activate', self.on_menuitem_set_stop_seed_at_ratio_disable) submenu.append(item) item = gtk.MenuItem(_('Enable...')) item.set_name('menuitem_stop_seed_at_ratio') item.connect('activate', self.on_menuitem_set_other) submenu.append(item) submenu.show_all() self.builder.get_object('menuitem_stop_seed_at_ratio').set_submenu(submenu) self.torrentmenu = self.builder.get_object('torrent_menu') self.menu_torrent = self.main_builder.get_object('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.main_builder.get_object('menuitem_toolbar').set_active(self.config['show_toolbar']) self.main_builder.get_object('menuitem_sidebar').set_active(self.config['show_sidebar']) self.main_builder.get_object('menuitem_statusbar').set_active(self.config['show_statusbar']) self.main_builder.get_object('sidebar_show_zero').set_active(self.config['sidebar_show_zero']) self.main_builder.get_object('sidebar_show_trackers').set_active(self.config['sidebar_show_trackers']) self.main_builder.get_object('sidebar_show_owners').set_active(self.config['sidebar_show_owners']) # Connect main window Signals # self.mainwindow.connect_signals(self) # Connect menubar signals self.builder.connect_signals(self) self.change_sensitivity = [ 'menuitem_addtorrent' ] def start(self): for widget in self.change_sensitivity: self.main_builder.get_object(widget).set_sensitive(True) # Only show open_folder menuitem and separator if connected to a localhost daemon. localhost_items = [ 'menuitem_open_folder', 'separator4' ] if client.is_localhost(): for widget in localhost_items: self.builder.get_object(widget).show() self.builder.get_object(widget).set_no_show_all(False) else: for widget in localhost_items: self.builder.get_object(widget).hide() self.builder.get_object(widget).set_no_show_all(True) self.main_builder.get_object('separatormenuitem').set_visible(not self.config['standalone']) self.main_builder.get_object('menuitem_quitdaemon').set_visible(not self.config['standalone']) self.main_builder.get_object('menuitem_connectionmanager').set_visible(not self.config['standalone']) # 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) 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 stop(self): log.debug('MenuBar stopping') client.deregister_event_handler('TorrentStateChangedEvent', self.on_torrentstatechanged_event) client.deregister_event_handler('TorrentResumedEvent', self.on_torrentresumed_event) client.deregister_event_handler('SessionPausedEvent', self.on_sessionpaused_event) client.deregister_event_handler('SessionResumedEvent', self.on_sessionresumed_event) for widget in self.change_sensitivity: self.main_builder.get_object(widget).set_sensitive(False) # Hide the Torrent menu self.menu_torrent.hide() self.main_builder.get_object('separatormenuitem').hide() self.main_builder.get_object('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 deluge.ui.gtkui.createtorrentdialog import CreateTorrentDialog CreateTorrentDialog().show() def on_menuitem_quitdaemon_activate(self, data=None): log.debug('on_menuitem_quitdaemon_activate') self.mainwindow.quit(shutdown=True) def on_menuitem_quit_activate(self, data=None): log.debug('on_menuitem_quit_activate') self.mainwindow.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 deluge.ui.gtkui.edittrackersdialog import EditTrackersDialog dialog = EditTrackersDialog( component.get('TorrentView').get_selected_torrent(), self.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 deluge.ui.gtkui.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): timestamp = gtk.get_current_event_time() path = os.path.join(status['download_location'], status['files'][0]['path'].split('/')[0]) deluge.common.show_file(path, timestamp=timestamp) for torrent_id in component.get('TorrentView').get_selected_torrents(): component.get('SessionProxy').get_torrent_status( torrent_id, ['download_location', 'files']).addCallback(_on_torrent_status) def on_menuitem_move_activate(self, data=None): log.debug('on_menuitem_move_activate') component.get('SessionProxy').get_torrent_status( component.get('TorrentView').get_selected_torrent(), ['download_location']).addCallback(self.show_move_storage_dialog) def show_move_storage_dialog(self, status): log.debug('show_move_storage_dialog') builder = gtk.Builder() builder.add_from_file(deluge.common.resource_filename( 'deluge.ui.gtkui', os.path.join('glade', 'move_storage_dialog.ui') )) # Keep it referenced: # https://bugzilla.gnome.org/show_bug.cgi?id=546802 self.move_storage_dialog = builder.get_object('move_storage_dialog') self.move_storage_dialog.set_transient_for(self.mainwindow.window) self.move_storage_dialog_hbox = builder.get_object('hbox_entry') self.move_storage_path_chooser = PathChooser('move_completed_paths_list') self.move_storage_dialog_hbox.add(self.move_storage_path_chooser) self.move_storage_dialog_hbox.show_all() self.move_storage_path_chooser.set_text(status['download_location']) def on_dialog_response_event(widget, response_id): def on_core_result(result): # Delete references self.move_storage_dialog.hide() del self.move_storage_dialog del self.move_storage_dialog_hbox if response_id == gtk.RESPONSE_CANCEL: on_core_result(None) if response_id == gtk.RESPONSE_OK: log.debug('Moving torrents to %s', self.move_storage_path_chooser.get_text()) path = self.move_storage_path_chooser.get_text() client.core.move_storage( component.get('TorrentView').get_selected_torrents(), path ).addCallback(on_core_result) 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 deluge.ui.gtkui.aboutdialog import AboutDialog AboutDialog().run() def on_menuitem_set_unlimited(self, widget): log.debug('widget name: %s', widget.get_name()) funcs = { 'menuitem_down_speed': 'max_download_speed', 'menuitem_up_speed': 'max_upload_speed', 'menuitem_max_connections': 'max_connections', 'menuitem_upload_slots': 'max_upload_slots' } if widget.get_name() in funcs: torrent_ids = component.get('TorrentView').get_selected_torrents() client.core.set_torrent_options(torrent_ids, {funcs[widget.get_name()]: -1}) def on_menuitem_set_other(self, widget): log.debug('widget name: %s', widget.get_name()) status_map = { 'menuitem_down_speed': ['max_download_speed', 'max_download_speed'], 'menuitem_up_speed': ['max_upload_speed', 'max_upload_speed'], 'menuitem_max_connections': ['max_connections', 'max_connections_global'], 'menuitem_upload_slots': ['max_upload_slots', 'max_upload_slots_global'], 'menuitem_stop_seed_at_ratio': ['stop_ratio', 'stop_seed_ratio'] } other_dialog_info = { 'menuitem_down_speed': [_('Download Speed Limit'), _('Set the maximum download speed'), _('KiB/s'), 'downloading.svg'], 'menuitem_up_speed': [_('Upload Speed Limit'), _('Set the maximum upload speed'), _('KiB/s'), 'seeding.svg'], 'menuitem_max_connections': [_('Incoming Connections'), _('Set the maximum incoming connections'), '', gtk.STOCK_NETWORK], 'menuitem_upload_slots': [_('Peer Upload Slots'), _('Set the maximum upload slots'), '', gtk.STOCK_SORT_ASCENDING], 'menuitem_stop_seed_at_ratio': [_('Stop Seed At Ratio'), 'Stop torrent seeding at ratio', '', None] } core_key = status_map[widget.get_name()][0] core_key_global = status_map[widget.get_name()][1] def _on_torrent_status(status): other_dialog = other_dialog_info[widget.get_name()] # Add the default using status value if status: other_dialog.append(status[core_key_global]) def set_value(value): if value is not None: if value == 0: value += -1 options = {core_key: value} if core_key == 'stop_ratio': options['stop_at_ratio'] = True client.core.set_torrent_options(torrent_ids, options) dialog = OtherDialog(*other_dialog) dialog.run().addCallback(set_value) torrent_ids = component.get('TorrentView').get_selected_torrents() if len(torrent_ids) == 1: core_key_global = core_key d = component.get('SessionProxy').get_torrent_status(torrent_ids[0], [core_key]) else: d = client.core.get_config_values([core_key_global]) d.addCallback(_on_torrent_status) def on_menuitem_set_automanaged_on(self, widget): client.core.set_torrent_options(component.get('TorrentView').get_selected_torrents(), {'auto_managed': True}) def on_menuitem_set_automanaged_off(self, widget): client.core.set_torrent_options(component.get('TorrentView').get_selected_torrents(), {'auto_managed': False}) def on_menuitem_set_stop_seed_at_ratio_disable(self, widget): client.core.set_torrent_options(component.get('TorrentView').get_selected_torrents(), {'stop_at_ratio': 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_menuitem_sidebar_owners_toggled(self, widget): self.config['sidebar_show_owners'] = widget.get_active() component.get('FilterTreeView').update() 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().items(): 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.builder.get_object('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(group=maingroup) for account in known_accounts: username = account['username'] item = gtk.RadioMenuItem(group=maingroup, label=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.builder.get_object('menuitem_change_owner').connect( 'activate', self._on_change_owner_submenu_active ) self.builder.get_object('menuitem_change_owner').set_submenu(self.change_owner_submenu) def _on_known_accounts_fail(self, reason): self.builder.get_object('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.items(): 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): ErrorDialog( _('Ownership Change Error'), _('There was an error while trying changing ownership.'), self.mainwindow.window, details=failure.value.logable() ).run() client.core.set_torrent_options( update_torrents, {'owner': username}).addErrback(failed_change_owner)
class AddTorrentDialog(component.Component): def __init__(self): component.Component.__init__(self, 'AddTorrentDialog') self.builder = gtk.Builder() # The base dialog self.builder.add_from_file(deluge.common.resource_filename( 'deluge.ui.gtkui', os.path.join('glade', 'add_torrent_dialog.ui') )) # The infohash dialog self.builder.add_from_file(deluge.common.resource_filename( 'deluge.ui.gtkui', os.path.join('glade', 'add_torrent_dialog.infohash.ui') )) # The url dialog self.builder.add_from_file(deluge.common.resource_filename( 'deluge.ui.gtkui', os.path.join('glade', 'add_torrent_dialog.url.ui') )) self.dialog = self.builder.get_object('dialog_add_torrent') self.dialog.connect('delete-event', self._on_delete_event) self.builder.connect_signals(self) # download?, path, filesize, sequence number, inconsistent? self.files_treestore = gtk.TreeStore( bool, str, TYPE_UINT64, TYPE_INT64, bool, str) self.files_treestore.set_sort_column_id(1, gtk.SORT_ASCENDING) # Holds the files info self.files = {} self.infos = {} self.core_config = {} self.options = {} self.previous_selected_torrent = None self.listview_torrents = self.builder.get_object('listview_torrents') self.listview_files = self.builder.get_object('listview_files') render = gtk.CellRendererText() render.connect('edited', self._on_torrent_name_edit) render.set_property('editable', True) column = gtk.TreeViewColumn(_('Torrent'), render, text=1) self.listview_torrents.append_column(column) render = gtk.CellRendererToggle() render.connect('toggled', self._on_file_toggled) column = gtk.TreeViewColumn(None, render, active=0, inconsistent=4) self.listview_files.append_column(column) column = gtk.TreeViewColumn(_('Filename')) render = gtk.CellRendererPixbuf() column.pack_start(render, False) column.add_attribute(render, 'stock-id', 5) render = gtk.CellRendererText() render.set_property('editable', True) render.connect('edited', self._on_filename_edited) column.pack_start(render, True) column.add_attribute(render, 'text', 1) column.set_expand(True) self.listview_files.append_column(column) render = gtk.CellRendererText() column = gtk.TreeViewColumn(_('Size')) column.pack_start(render, True) column.set_cell_data_func(render, cell_data_size, 2) self.listview_files.append_column(column) self.torrent_liststore = gtk.ListStore(str, str, str) self.listview_torrents.set_model(self.torrent_liststore) self.listview_torrents.set_tooltip_column(2) self.listview_files.set_model(self.files_treestore) self.listview_files.get_selection().set_mode(gtk.SELECTION_MULTIPLE) self.listview_torrents.get_selection().connect('changed', self._on_torrent_changed) self.setup_move_completed_path_chooser() self.setup_download_location_path_chooser() # Get default config values from the core self.core_keys = [ 'pre_allocate_storage', 'max_connections_per_torrent', 'max_upload_slots_per_torrent', 'max_upload_speed_per_torrent', 'max_download_speed_per_torrent', 'prioritize_first_last_pieces', 'sequential_download', 'add_paused', 'download_location', 'download_location_paths_list', 'move_completed', 'move_completed_path', 'move_completed_paths_list', ] # self.core_keys += self.move_completed_path_chooser.get_config_keys() self.builder.get_object('notebook1').connect('switch-page', self._on_switch_page) def start(self): self.update_core_config() def show(self, focus=False): return self.update_core_config(True, focus) def _show(self, focus=False): if component.get('MainWindow').is_on_active_workspace(): self.dialog.set_transient_for(component.get('MainWindow').window) else: self.dialog.set_transient_for(None) self.dialog.present() if focus: self.dialog.window.focus() return None def hide(self): self.dialog.hide() self.files = {} self.infos = {} self.options = {} self.previous_selected_torrent = None self.torrent_liststore.clear() self.files_treestore.clear() self.dialog.set_transient_for(component.get('MainWindow').window) return None def update_core_config(self, show=False, focus=False): def _on_config_values(config): self.core_config = config if self.core_config: self.set_default_options() if show: self._show(focus) # Send requests to the core for these config values return client.core.get_config_values(self.core_keys).addCallback(_on_config_values) def add_from_files(self, filenames): new_row = None already_added = 0 for filename in filenames: # Get the torrent data from the torrent file try: info = TorrentInfo(filename) except Exception as ex: log.debug('Unable to open torrent file: %s', ex) ErrorDialog(_('Invalid File'), ex, self.dialog).run() continue if info.info_hash in self.files: already_added += 1 continue new_row = self.torrent_liststore.append([info.info_hash, info.name, xml_escape(filename)]) self.files[info.info_hash] = info.files self.infos[info.info_hash] = info.filedata self.listview_torrents.get_selection().select_iter(new_row) self.set_default_options() self.save_torrent_options(new_row) (model, row) = self.listview_torrents.get_selection().get_selected() if not row and new_row: self.listview_torrents.get_selection().select_iter(new_row) self.dialog.set_title(_('Add Torrents (%d)') % len(self.torrent_liststore)) if already_added: log.debug('Tried to add %d duplicate torrents!', already_added) ErrorDialog( _('Duplicate Torrent(s)'), _('You cannot add the same torrent twice. %d torrents were already added.' % already_added), self.dialog ).run() def add_from_magnets(self, uris): new_row = None for uri in uris: magnet = deluge.common.get_magnet_info(uri) if not magnet: log.error('Invalid magnet: %s', uri) continue if magnet['info_hash'] in self.infos: log.info('Torrent already in Add Dialog list: %s', uri) continue new_row = self.torrent_liststore.append([magnet['info_hash'], magnet['name'], xml_escape(uri)]) self.files[magnet['info_hash']] = magnet['files_tree'] self.infos[magnet['info_hash']] = None self.listview_torrents.get_selection().select_iter(new_row) self.set_default_options() self.save_torrent_options(new_row) (model, row) = self.listview_torrents.get_selection().get_selected() if not row and new_row: self.listview_torrents.get_selection().select_iter(new_row) def _on_torrent_changed(self, treeselection): (model, row) = treeselection.get_selected() if row is None or not model.iter_is_valid(row): self.files_treestore.clear() self.previous_selected_torrent = None return if model[row][0] not in self.files: self.files_treestore.clear() self.previous_selected_torrent = None return # Save the previous torrents options self.save_torrent_options() # Update files list files_list = self.files[model.get_value(row, 0)] self.prepare_file_store(files_list) if self.core_config == {}: self.update_core_config() # Update the options frame self.update_torrent_options(model.get_value(row, 0)) self.previous_selected_torrent = row def _on_torrent_name_edit(self, w, row, new_name): # TODO: Update torrent name pass def _on_switch_page(self, widget, page, page_num): # Save the torrent options when switching notebook pages self.save_torrent_options() def prepare_file_store(self, files): with listview_replace_treestore(self.listview_files): split_files = {} for i, _file in enumerate(files): self.prepare_file( _file, _file['path'], i, _file['download'], split_files ) self.add_files(None, split_files) self.listview_files.expand_row(b'0', False) def prepare_file(self, _file, file_name, file_num, download, files_storage): first_slash_index = file_name.find(os.path.sep) if first_slash_index == -1: files_storage[file_name] = (file_num, _file, download) else: file_name_chunk = file_name[:first_slash_index + 1] if file_name_chunk not in files_storage: files_storage[file_name_chunk] = {} self.prepare_file(_file, file_name[first_slash_index + 1:], file_num, download, files_storage[file_name_chunk]) def add_files(self, parent_iter, split_files): ret = 0 for key, value in split_files.items(): if key.endswith(os.path.sep): chunk_iter = self.files_treestore.append( parent_iter, [True, key, 0, -1, False, gtk.STOCK_DIRECTORY]) chunk_size = self.add_files(chunk_iter, value) self.files_treestore.set(chunk_iter, 2, chunk_size) ret += chunk_size else: self.files_treestore.append(parent_iter, [ value[2], key, value[1]['size'], value[0], False, gtk.STOCK_FILE ]) ret += value[1]['size'] if parent_iter and self.files_treestore.iter_has_child(parent_iter): # Iterate through the children and see what we should label the # folder, download true, download false or inconsistent. itr = self.files_treestore.iter_children(parent_iter) download = [] download_value = False inconsistent = False while itr: download.append(self.files_treestore.get_value(itr, 0)) itr = self.files_treestore.iter_next(itr) if sum(download) == len(download): download_value = True elif sum(download) == 0: download_value = False else: inconsistent = True self.files_treestore.set_value(parent_iter, 0, download_value) self.files_treestore.set_value(parent_iter, 4, inconsistent) return ret def load_path_choosers_data(self): self.move_completed_path_chooser.set_text(self.core_config['move_completed_path'], cursor_end=False, default_text=True) self.download_location_path_chooser.set_text(self.core_config['download_location'], cursor_end=False, default_text=True) self.builder.get_object('chk_move_completed').set_active(self.core_config['move_completed']) def setup_move_completed_path_chooser(self): self.move_completed_hbox = self.builder.get_object('hbox_move_completed_chooser') self.move_completed_path_chooser = PathChooser('move_completed_paths_list') self.move_completed_hbox.add(self.move_completed_path_chooser) self.move_completed_hbox.show_all() def setup_download_location_path_chooser(self): self.download_location_hbox = self.builder.get_object('hbox_download_location_chooser') self.download_location_path_chooser = PathChooser('download_location_paths_list') self.download_location_hbox.add(self.download_location_path_chooser) self.download_location_hbox.show_all() def update_torrent_options(self, torrent_id): if torrent_id not in self.options: self.set_default_options() return options = self.options[torrent_id] self.download_location_path_chooser.set_text(options['download_location'], cursor_end=True) self.move_completed_path_chooser.set_text(options['move_completed_path'], cursor_end=True) self.builder.get_object('spin_maxdown').set_value( options['max_download_speed']) self.builder.get_object('spin_maxup').set_value( options['max_upload_speed']) self.builder.get_object('spin_maxconnections').set_value( options['max_connections']) self.builder.get_object('spin_maxupslots').set_value( options['max_upload_slots']) self.builder.get_object('chk_paused').set_active( options['add_paused']) self.builder.get_object('chk_pre_alloc').set_active( options['pre_allocate_storage']) self.builder.get_object('chk_prioritize').set_active( options['prioritize_first_last_pieces']) self.builder.get_object('chk_sequential_download').set_active( options['sequential_download']) self.builder.get_object('chk_move_completed').set_active( options['move_completed']) def save_torrent_options(self, row=None): # Keeps the torrent options dictionary up-to-date with what the user has # selected. if row is None: if self.previous_selected_torrent and \ self.torrent_liststore.iter_is_valid(self.previous_selected_torrent): row = self.previous_selected_torrent else: return torrent_id = self.torrent_liststore.get_value(row, 0) if torrent_id in self.options: options = self.options[torrent_id] else: options = {} options['download_location'] = self.download_location_path_chooser.get_text() options['move_completed_path'] = self.move_completed_path_chooser.get_text() options['pre_allocate_storage'] = self.builder.get_object('chk_pre_alloc').get_active() options['move_completed'] = self.builder.get_object('chk_move_completed').get_active() options['max_download_speed'] = self.builder.get_object('spin_maxdown').get_value() options['max_upload_speed'] = self.builder.get_object('spin_maxup').get_value() options['max_connections'] = self.builder.get_object('spin_maxconnections').get_value_as_int() options['max_upload_slots'] = self.builder.get_object('spin_maxupslots').get_value_as_int() options['add_paused'] = self.builder.get_object('chk_paused').get_active() options['prioritize_first_last_pieces'] = self.builder.get_object('chk_prioritize').get_active() options['sequential_download'] = self.builder.get_object( 'chk_sequential_download').get_active() or False options['move_completed'] = self.builder.get_object('chk_move_completed').get_active() options['seed_mode'] = self.builder.get_object('chk_seed_mode').get_active() self.options[torrent_id] = options # Save the file priorities files_priorities = self.build_priorities( self.files_treestore.get_iter_first(), {} ) if len(files_priorities) > 0: for i, file_dict in enumerate(self.files[torrent_id]): file_dict['download'] = files_priorities[i] def build_priorities(self, _iter, priorities): while _iter is not None: if self.files_treestore.iter_has_child(_iter): self.build_priorities(self.files_treestore.iter_children(_iter), priorities) elif not self.files_treestore.get_value(_iter, 1).endswith(os.path.sep): priorities[self.files_treestore.get_value(_iter, 3)] = self.files_treestore.get_value(_iter, 0) _iter = self.files_treestore.iter_next(_iter) return priorities def set_default_options(self): if not self.core_config: # update_core_config will call this method again. self.update_core_config() return self.load_path_choosers_data() self.builder.get_object('chk_pre_alloc').set_active( self.core_config['pre_allocate_storage']) self.builder.get_object('spin_maxdown').set_value( self.core_config['max_download_speed_per_torrent']) self.builder.get_object('spin_maxup').set_value( self.core_config['max_upload_speed_per_torrent']) self.builder.get_object('spin_maxconnections').set_value( self.core_config['max_connections_per_torrent']) self.builder.get_object('spin_maxupslots').set_value( self.core_config['max_upload_slots_per_torrent']) self.builder.get_object('chk_paused').set_active( self.core_config['add_paused']) self.builder.get_object('chk_prioritize').set_active( self.core_config['prioritize_first_last_pieces']) self.builder.get_object('chk_sequential_download').set_active( self.core_config['sequential_download']) self.builder.get_object('chk_move_completed').set_active( self.core_config['move_completed']) self.builder.get_object('chk_seed_mode').set_active(False) def get_file_priorities(self, torrent_id): # A list of priorities files_list = [] for file_dict in self.files[torrent_id]: if not file_dict['download']: files_list.append(0) else: files_list.append(1) return files_list def _on_file_toggled(self, render, path): (model, paths) = self.listview_files.get_selection().get_selected_rows() if len(paths) > 1: for path in paths: row = model.get_iter(path) self.toggle_iter(row) else: row = model.get_iter(path) self.toggle_iter(row) self.update_treeview_toggles(self.files_treestore.get_iter_first()) def toggle_iter(self, _iter, toggle_to=None): if toggle_to is None: toggle_to = not self.files_treestore.get_value(_iter, 0) self.files_treestore.set_value(_iter, 0, toggle_to) if self.files_treestore.iter_has_child(_iter): child = self.files_treestore.iter_children(_iter) while child is not None: self.toggle_iter(child, toggle_to) child = self.files_treestore.iter_next(child) def update_treeview_toggles(self, _iter): toggle_inconsistent = -1 this_level_toggle = None while _iter is not None: if self.files_treestore.iter_has_child(_iter): toggle = self.update_treeview_toggles(self.files_treestore.iter_children(_iter)) if toggle == toggle_inconsistent: self.files_treestore.set_value(_iter, 4, True) else: self.files_treestore.set_value(_iter, 0, toggle) # set inconsistent to false self.files_treestore.set_value(_iter, 4, False) else: toggle = self.files_treestore.get_value(_iter, 0) if this_level_toggle is None: this_level_toggle = toggle elif this_level_toggle != toggle: this_level_toggle = toggle_inconsistent _iter = self.files_treestore.iter_next(_iter) return this_level_toggle def on_button_file_clicked(self, widget): log.debug('on_button_file_clicked') # Setup the filechooserdialog chooser = gtk.FileChooserDialog( _('Choose a .torrent file'), None, gtk.FILE_CHOOSER_ACTION_OPEN, buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_OK) ) chooser.set_transient_for(self.dialog) chooser.set_select_multiple(True) chooser.set_property('skip-taskbar-hint', True) chooser.set_local_only(False) # Add .torrent and * file filters file_filter = gtk.FileFilter() file_filter.set_name(_('Torrent files')) file_filter.add_pattern('*.' + 'torrent') chooser.add_filter(file_filter) file_filter = gtk.FileFilter() file_filter.set_name(_('All files')) file_filter.add_pattern('*') chooser.add_filter(file_filter) # Load the 'default_load_path' from the config self.config = ConfigManager('gtkui.conf') if self.config['default_load_path'] is not None: chooser.set_current_folder(self.config['default_load_path']) # Run the dialog response = chooser.run() if response == gtk.RESPONSE_OK: result = chooser.get_filenames() self.config['default_load_path'] = chooser.get_current_folder() else: chooser.destroy() return chooser.destroy() self.add_from_files(result) def on_button_url_clicked(self, widget): log.debug('on_button_url_clicked') dialog = self.builder.get_object('url_dialog') entry = self.builder.get_object('entry_url') dialog.set_default_response(gtk.RESPONSE_OK) dialog.set_transient_for(self.dialog) entry.grab_focus() text = get_clipboard_text() if text and deluge.common.is_url(text) or deluge.common.is_magnet(text): entry.set_text(text) dialog.show_all() response = dialog.run() if response == gtk.RESPONSE_OK: url = entry.get_text().decode('utf-8') else: url = None entry.set_text('') dialog.hide() # This is where we need to fetch the .torrent file from the URL and # add it to the list. log.debug('url: %s', url) if url: if deluge.common.is_url(url): self.add_from_url(url) elif deluge.common.is_magnet(url): self.add_from_magnets([url]) else: ErrorDialog( _('Invalid URL'), '%s %s' % (url, _('is not a valid URL.')), self.dialog ).run() def add_from_url(self, url): dialog = gtk.Dialog( _('Downloading...'), flags=gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_NO_SEPARATOR, parent=self.dialog) dialog.set_transient_for(self.dialog) pb = gtk.ProgressBar() dialog.vbox.pack_start(pb, True, True, 0) dialog.show_all() # Create a tmp file path import tempfile tmp_fd, tmp_file = tempfile.mkstemp(prefix='deluge_url.', suffix='.torrent') def on_part(data, current_length, total_length): if total_length: percent = current_length / total_length pb.set_fraction(percent) pb.set_text('%.2f%% (%s / %s)' % ( percent * 100, deluge.common.fsize(current_length), deluge.common.fsize(total_length))) else: pb.pulse() pb.set_text('%s' % deluge.common.fsize(current_length)) def on_download_success(result): self.add_from_files([result]) dialog.destroy() def on_download_fail(result): log.debug('Download failed: %s', result) dialog.destroy() ErrorDialog( _('Download Failed'), '%s %s' % (_('Failed to download:'), url), details=result.getErrorMessage(), parent=self.dialog ).run() return result d = download_file(url, tmp_file, on_part) os.close(tmp_fd) d.addCallbacks(on_download_success, on_download_fail) def on_button_hash_clicked(self, widget): log.debug('on_button_hash_clicked') dialog = self.builder.get_object('dialog_infohash') entry = self.builder.get_object('entry_hash') textview = self.builder.get_object('text_trackers') dialog.set_default_response(gtk.RESPONSE_OK) dialog.set_transient_for(self.dialog) entry.grab_focus() text = get_clipboard_text() if deluge.common.is_infohash(text): entry.set_text(text) dialog.show_all() response = dialog.run() infohash = entry.get_text().strip() if response == gtk.RESPONSE_OK and deluge.common.is_infohash(infohash): # Create a list of trackers from the textview buffer tview_buf = textview.get_buffer() trackers_text = tview_buf.get_text(*tview_buf.get_bounds()) log.debug('Create torrent tracker lines: %s', trackers_text) trackers = list(trackers_tiers_from_text(trackers_text).keys()) # Convert the information to a magnet uri, this is just easier to # handle this way. log.debug('trackers: %s', trackers) magnet = deluge.common.create_magnet_uri(infohash, infohash, trackers) log.debug('magnet uri: %s', magnet) self.add_from_magnets([magnet]) entry.set_text('') textview.get_buffer().set_text('') dialog.hide() def on_button_remove_clicked(self, widget): log.debug('on_button_remove_clicked') (model, row) = self.listview_torrents.get_selection().get_selected() if row is None: return torrent_id = model.get_value(row, 0) model.remove(row) del self.files[torrent_id] del self.infos[torrent_id] def on_button_trackers_clicked(self, widget): log.debug('on_button_trackers_clicked') def on_button_cancel_clicked(self, widget): log.debug('on_button_cancel_clicked') self.hide() def on_button_add_clicked(self, widget): log.debug('on_button_add_clicked') self.add_torrents() self.hide() def add_torrents(self): (model, row) = self.listview_torrents.get_selection().get_selected() if row is not None: self.save_torrent_options(row) torrents_to_add = [] row = self.torrent_liststore.get_iter_first() while row is not None: torrent_id = self.torrent_liststore.get_value(row, 0) filename = xml_unescape(self.torrent_liststore.get_value(row, 2)) try: options = self.options[torrent_id] except KeyError: options = None file_priorities = self.get_file_priorities(torrent_id) if options is not None: options['file_priorities'] = file_priorities if deluge.common.is_magnet(filename): del options['file_priorities'] client.core.add_torrent_magnet(filename, options) else: torrents_to_add.append((os.path.split(filename)[-1], base64.encodestring(self.infos[torrent_id]), options)) row = self.torrent_liststore.iter_next(row) def on_torrents_added(errors): if errors: log.info('Failed to add %d out of %d torrents.', len(errors), len(torrents_to_add)) for e in errors: log.info('Torrent add failed: %s', e) else: log.info('Successfully added %d torrents.', len(torrents_to_add)) client.core.add_torrent_files(torrents_to_add).addCallback(on_torrents_added) def on_button_apply_clicked(self, widget): log.debug('on_button_apply_clicked') (model, row) = self.listview_torrents.get_selection().get_selected() if row is None: return self.save_torrent_options(row) # The options, except file renames, we want all the torrents to have options = self.options[model.get_value(row, 0)].copy() options.pop('mapped_files', None) # Set all the torrent options row = model.get_iter_first() while row is not None: torrent_id = model.get_value(row, 0) self.options[torrent_id].update(options) row = model.iter_next(row) def on_button_revert_clicked(self, widget): log.debug('on_button_revert_clicked') (model, row) = self.listview_torrents.get_selection().get_selected() if row is None: return del self.options[model.get_value(row, 0)] self.set_default_options() def on_chk_move_completed_toggled(self, widget): value = widget.get_active() self.move_completed_path_chooser.set_sensitive(value) def _on_delete_event(self, widget, event): self.hide() return True def get_file_path(self, row, path=''): if not row: return path path = self.files_treestore[row][1] + path return self.get_file_path(self.files_treestore.iter_parent(row), path) def _on_filename_edited(self, renderer, path, new_text): index = self.files_treestore[path][3] new_text = new_text.strip(os.path.sep).strip() # Return if the text hasn't changed if new_text == self.files_treestore[path][1]: return # Get the tree iter itr = self.files_treestore.get_iter(path) # Get the torrent_id (model, row) = self.listview_torrents.get_selection().get_selected() torrent_id = model[row][0] if 'mapped_files' not in self.options[torrent_id]: self.options[torrent_id]['mapped_files'] = {} if index > -1: # We're renaming a file! Yay! That's easy! if not new_text: return parent = self.files_treestore.iter_parent(itr) file_path = os.path.join(self.get_file_path(parent), new_text) # Don't rename if filename exists if parent: for row in self.files_treestore[parent].iterchildren(): if new_text == row[1]: return if os.path.sep in new_text: # There are folders in this path, so we need to create them # and then move the file iter to top split_text = new_text.split(os.path.sep) for s in split_text[:-1]: parent = self.files_treestore.append(parent, [True, s, 0, -1, False, gtk.STOCK_DIRECTORY]) self.files_treestore[itr][1] = split_text[-1] reparent_iter(self.files_treestore, itr, parent) else: # Update the row's text self.files_treestore[itr][1] = new_text # Update the mapped_files dict in the options with the index and new # file path. # We'll send this to the core when adding the torrent so it knows # what to rename before adding. self.options[torrent_id]['mapped_files'][index] = file_path self.files[torrent_id][index]['path'] = file_path else: # Folder! def walk_tree(row): if not row: return # Get the file path base once, since it will be the same for # all siblings file_path_base = self.get_file_path( self.files_treestore.iter_parent(row) ) # Iterate through all the siblings at this level while row: # We recurse if there are children if self.files_treestore.iter_has_child(row): walk_tree(self.files_treestore.iter_children(row)) index = self.files_treestore[row][3] if index > -1: # Get the new full path for this file file_path = file_path_base + self.files_treestore[row][1] # Update the file path in the mapped_files dict self.options[torrent_id]['mapped_files'][index] = file_path self.files[torrent_id][index]['path'] = file_path # Get the next siblings iter row = self.files_treestore.iter_next(row) # Update the treestore row first so that when walking the tree # we can construct the new proper paths # We need to check if this folder has been split if os.path.sep in new_text: # It's been split, so we need to add new folders and then re-parent # itr. parent = self.files_treestore.iter_parent(itr) split_text = new_text.split(os.path.sep) for s in split_text[:-1]: # We don't iterate over the last item because we'll just use # the existing itr and change the text parent = self.files_treestore.append(parent, [ True, s + os.path.sep, 0, -1, False, gtk.STOCK_DIRECTORY ]) self.files_treestore[itr][1] = split_text[-1] + os.path.sep # Now re-parent itr to parent reparent_iter(self.files_treestore, itr, parent) itr = parent # We need to re-expand the view because it might contracted # if we change the root iter self.listview_files.expand_row(b'0', False) else: # This was a simple folder rename without any splits, so just # change the path for itr self.files_treestore[itr][1] = new_text + os.path.sep # Walk through the tree from 'itr' and add all the new file paths # to the 'mapped_files' option walk_tree(itr)
def setup_download_location_path_chooser(self): self.download_location_hbox = self.builder.get_object('hbox_download_location_chooser') self.download_location_path_chooser = PathChooser('download_location_paths_list') self.download_location_hbox.add(self.download_location_path_chooser) self.download_location_hbox.show_all()
def setup_move_completed_path_chooser(self): self.move_completed_hbox = self.builder.get_object('hbox_move_completed_chooser') self.move_completed_path_chooser = PathChooser('move_completed_paths_list') self.move_completed_hbox.add(self.move_completed_path_chooser) self.move_completed_hbox.show_all()