class PeersTab(Tab): def __init__(self): super(PeersTab, self).__init__('Peers', 'peers_tab', 'peers_tab_label') self.peer_menu = self.main_builder.get_object('menu_peer_tab') component.get('MainWindow').connect_signals(self) self.listview = self.main_builder.get_object('peers_listview') self.listview.props.has_tooltip = True self.listview.connect('button-press-event', self._on_button_press_event) self.listview.connect('query-tooltip', self._on_query_tooltip) # flag, ip, client, downspd, upspd, country code, int_ip, seed/peer icon, progress self.liststore = ListStore(Pixbuf, str, str, int, int, str, float, Pixbuf, float) self.cached_flag_pixbufs = {} self.seed_pixbuf = icon_seeding self.peer_pixbuf = icon_downloading # key is ip address, item is row iter self.peers = {} # Country column column = TreeViewColumn() render = CellRendererPixbuf() column.pack_start(render, False) column.add_attribute(render, 'pixbuf', 0) column.set_sort_column_id(5) column.set_clickable(True) column.set_resizable(True) column.set_expand(False) column.set_min_width(20) column.set_reorderable(True) self.listview.append_column(column) # Address column column = TreeViewColumn(_('Address')) render = CellRendererPixbuf() column.pack_start(render, False) column.add_attribute(render, 'pixbuf', 7) render = CellRendererText() column.pack_start(render, False) column.add_attribute(render, 'text', 1) column.set_sort_column_id(6) column.set_clickable(True) column.set_resizable(True) column.set_expand(False) column.set_min_width(100) column.set_reorderable(True) self.listview.append_column(column) # Client column column = TreeViewColumn(_('Client')) render = CellRendererText() column.pack_start(render, False) column.add_attribute(render, 'text', 2) column.set_sort_column_id(2) column.set_clickable(True) column.set_resizable(True) column.set_expand(False) column.set_min_width(100) column.set_reorderable(True) self.listview.append_column(column) # Progress column column = TreeViewColumn(_('Progress')) render = CellRendererProgress() column.pack_start(render, True) column.set_cell_data_func(render, cell_data_peer_progress, 8) column.set_sort_column_id(8) column.set_clickable(True) column.set_resizable(True) column.set_expand(False) column.set_min_width(100) column.set_reorderable(True) self.listview.append_column(column) # Down Speed column column = TreeViewColumn(_('Down Speed')) render = CellRendererText() column.pack_start(render, False) column.set_cell_data_func(render, cell_data_speed_down, 3) column.set_sort_column_id(3) column.set_clickable(True) column.set_resizable(True) column.set_expand(False) column.set_min_width(50) column.set_reorderable(True) self.listview.append_column(column) # Up Speed column column = TreeViewColumn(_('Up Speed')) render = CellRendererText() column.pack_start(render, False) column.set_cell_data_func(render, cell_data_speed_up, 4) column.set_sort_column_id(4) column.set_clickable(True) column.set_resizable(True) column.set_expand(False) column.set_min_width(50) # Bugfix: Last column needs max_width set to stop scrollbar appearing column.set_max_width(150) column.set_reorderable(True) self.listview.append_column(column) self.listview.set_model(self.liststore) self.load_state() self.torrent_id = None def save_state(self): # Get the current sort order of the view column_id, sort_order = self.liststore.get_sort_column_id() # Setup state dict state = { 'columns': {}, 'sort_id': column_id, 'sort_order': int(sort_order) if sort_order else None, } for index, column in enumerate(self.listview.get_columns()): state['columns'][column.get_title()] = { 'position': index, 'width': column.get_width(), } save_pickled_state_file('peers_tab.state', state) def load_state(self): state = load_pickled_state_file('peers_tab.state') if state is None: return if len(state['columns']) != len(self.listview.get_columns()): log.warning('peers_tab.state is not compatible! rejecting..') return if state['sort_id'] and state['sort_order'] is not None: self.liststore.set_sort_column_id(state['sort_id'], state['sort_order']) for (index, column) in enumerate(self.listview.get_columns()): cname = column.get_title() if cname in state['columns']: cstate = state['columns'][cname] column.set_sizing(TreeViewColumnSizing.FIXED) column.set_fixed_width( cstate['width'] if cstate['width'] > 0 else 10) if state['sort_id'] == index and state[ 'sort_order'] is not None: column.set_sort_indicator(True) column.set_sort_order(state['sort_order']) if cstate['position'] != index: # Column is in wrong position if cstate['position'] == 0: self.listview.move_column_after(column, None) elif (self.listview.get_columns()[cstate['position'] - 1].get_title() != cname): self.listview.move_column_after( column, self.listview.get_columns()[cstate['position'] - 1]) def update(self): # Get the first selected torrent torrent_id = component.get('TorrentView').get_selected_torrents() # Only use the first torrent in the list or return if None selected if len(torrent_id) != 0: torrent_id = torrent_id[0] else: # No torrent is selected in the torrentview self.liststore.clear() return if torrent_id != self.torrent_id: # We only want to do this if the torrent_id has changed self.liststore.clear() self.peers = {} self.torrent_id = torrent_id component.get('SessionProxy').get_torrent_status( torrent_id, ['peers']).addCallback(self._on_get_torrent_status) def get_flag_pixbuf(self, country): if not country.strip(): return None if country not in self.cached_flag_pixbufs: # We haven't created a pixbuf for this country yet try: self.cached_flag_pixbufs[country] = Pixbuf.new_from_file( deluge.common.resource_filename( 'deluge', os.path.join('ui', 'data', 'pixmaps', 'flags', country.lower() + '.png'), )) except Exception as ex: log.debug('Unable to load flag: %s', ex) return None return self.cached_flag_pixbufs[country] def _on_get_torrent_status(self, status): new_ips = set() for peer in status['peers']: new_ips.add(peer['ip']) if peer['ip'] in self.peers: # We already have this peer in our list, so lets just update it row = self.peers[peer['ip']] if not self.liststore.iter_is_valid(row): # This iter is invalid, delete it and continue to next iteration del self.peers[peer['ip']] continue values = self.liststore.get(row, 3, 4, 5, 7, 8) if peer['down_speed'] != values[0]: self.liststore.set_value(row, 3, peer['down_speed']) if peer['up_speed'] != values[1]: self.liststore.set_value(row, 4, peer['up_speed']) if peer['country'] != values[2]: self.liststore.set_value(row, 5, peer['country']) self.liststore.set_value( row, 0, self.get_flag_pixbuf(peer['country'])) if peer['seed']: icon = self.seed_pixbuf else: icon = self.peer_pixbuf if icon != values[3]: self.liststore.set_value(row, 7, icon) if peer['progress'] != values[4]: self.liststore.set_value(row, 8, peer['progress']) else: # Peer is not in list so we need to add it # Create an int IP address for sorting purposes if peer['ip'].count(':') == 1: # This is an IPv4 address ip_int = sum( int(byte) << shift for byte, shift in zip( peer['ip'].split(':')[0].split('.'), (24, 16, 8, 0))) peer_ip = peer['ip'] else: # This is an IPv6 address import socket import binascii # Split out the :port ip = ':'.join(peer['ip'].split(':')[:-1]) ip_int = int( binascii.hexlify(socket.inet_pton(socket.AF_INET6, ip)), 16) peer_ip = '[%s]:%s' % (ip, peer['ip'].split(':')[-1]) if peer['seed']: icon = self.seed_pixbuf else: icon = self.peer_pixbuf row = self.liststore.append([ self.get_flag_pixbuf(peer['country']), peer_ip, peer['client'], peer['down_speed'], peer['up_speed'], peer['country'], float(ip_int), icon, peer['progress'], ]) self.peers[peer['ip']] = row # Now we need to remove any ips that were not in status["peers"] list for ip in set(self.peers).difference(new_ips): self.liststore.remove(self.peers[ip]) del self.peers[ip] def clear(self): self.liststore.clear() def _on_button_press_event(self, widget, event): """This is a callback for showing the right-click context menu.""" log.debug('on_button_press_event') # We only care about right-clicks if self.torrent_id and event.button == 3: self.peer_menu.popup(None, None, None, None, event.button, event.time) return True def _on_query_tooltip(self, widget, x, y, keyboard_tip, tooltip): is_tooltip, x, y, model, path, _iter = widget.get_tooltip_context( x, y, keyboard_tip) if is_tooltip: country_code = model.get(_iter, 5)[0] if country_code != ' ' and country_code in COUNTRIES: tooltip.set_text(COUNTRIES[country_code]) # widget here is self.listview widget.set_tooltip_cell(tooltip, path, widget.get_column(0), None) return True return False def on_menuitem_add_peer_activate(self, menuitem): """This is a callback for manually adding a peer""" log.debug('on_menuitem_add_peer') builder = Builder() builder.add_from_file( deluge.common.resource_filename( __package__, os.path.join('glade', 'connect_peer_dialog.ui'))) peer_dialog = builder.get_object('connect_peer_dialog') txt_ip = builder.get_object('txt_ip') response = peer_dialog.run() if response: value = txt_ip.get_text() ip, port = parse_ip_port(value) if ip and port: log.info('Adding peer IP: %s port: %s to %s', ip, port, self.torrent_id) client.core.connect_peer(self.torrent_id, ip, port) else: log.error('Error parsing peer "%s"', value) peer_dialog.destroy() return True
class QueuedTorrents(component.Component): def __init__(self): component.Component.__init__( self, 'QueuedTorrents', depend=['StatusBar', 'AddTorrentDialog'] ) self.queue = [] self.status_item = None self.config = ConfigManager('gtk3ui.conf') self.builder = Builder() self.builder.add_from_file( deluge.common.resource_filename( __package__, os.path.join('glade', 'queuedtorrents.ui') ) ) self.builder.get_object('chk_autoadd').set_active(self.config['autoadd_queued']) self.dialog = self.builder.get_object('queued_torrents_dialog') self.dialog.set_icon(get_logo(32)) self.builder.connect_signals(self) self.treeview = self.builder.get_object('treeview') self.treeview.append_column( TreeViewColumn(_('Torrent'), CellRendererText(), text=0) ) self.liststore = ListStore(str, str) self.treeview.set_model(self.liststore) self.treeview.set_tooltip_column(1) def run(self): self.dialog.set_transient_for(component.get('MainWindow').window) self.dialog.show() def start(self): if len(self.queue) == 0: return # Make sure status bar info is showing self.update_status_bar() # We only want the add button sensitive if we're connected to a host self.builder.get_object('button_add').set_sensitive(True) if self.config['autoadd_queued'] or self.config['standalone']: self.on_button_add_clicked(None) else: self.run() def stop(self): # We only want the add button sensitive if we're connected to a host self.builder.get_object('button_add').set_sensitive(False) self.update_status_bar() def add_to_queue(self, torrents): """Adds the list of torrents to the queue""" # Add to the queue while removing duplicates self.queue = list(set(self.queue + torrents)) # Update the liststore self.liststore.clear() for torrent in self.queue: if deluge.common.is_magnet(torrent): magnet = deluge.common.get_magnet_info(torrent) self.liststore.append([magnet['name'], torrent]) else: self.liststore.append([os.path.split(torrent)[1], torrent]) # Update the status bar self.update_status_bar() def update_status_bar(self): """Attempts to update status bar""" # If there are no queued torrents.. remove statusbar widgets and return if len(self.queue) == 0: if self.status_item is not None: component.get('StatusBar').remove_item(self.status_item) self.status_item = None return False try: component.get('StatusBar') except Exception: # The statusbar hasn't been loaded yet, so we'll add a timer to # update it later. timeout_add(100, self.update_status_bar) return False # Set the label text for statusbar if len(self.queue) > 1: label = str(len(self.queue)) + _(' Torrents Queued') else: label = str(len(self.queue)) + _(' Torrent Queued') # Add the statusbar items if needed, or just modify the label if they # have already been added. if self.status_item is None: self.status_item = component.get('StatusBar').add_item( icon='view-sort-descending', text=label, callback=self.on_statusbar_click, ) else: self.status_item.set_text(label) # We return False so the timer stops return False def on_statusbar_click(self, widget, event): log.debug('on_statusbar_click') self.run() def on_button_remove_clicked(self, widget): selected = self.treeview.get_selection().get_selected()[1] if selected is not None: path = self.liststore.get_value(selected, 1) self.liststore.remove(selected) self.queue.remove(path) self.update_status_bar() def on_button_clear_clicked(self, widget): self.liststore.clear() del self.queue[:] self.update_status_bar() def on_button_close_clicked(self, widget): self.dialog.hide() def on_button_add_clicked(self, widget): # Add all the torrents in the liststore def add_torrent(model, path, _iter, data): torrent_path = deluge.common.decode_bytes(model.get_value(_iter, 1)) process_args([torrent_path]) self.liststore.foreach(add_torrent, None) del self.queue[:] self.dialog.hide() self.update_status_bar() def on_chk_autoadd_toggled(self, widget): self.config['autoadd_queued'] = widget.get_active()