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 AlbumsView: def __init__(self, widgets, extensions): self.widgets = widgets self.extensions = extensions self.functions = Functions() self.userconf = ConfigLoader() self.dblclick = None # Create the IconView self.albumview = self.widgets[1].get_object('albumview') self.albummodel = ListStore(Pixbuf, str, str, str, int, str, str) self.albumview.set_pixbuf_column(0) self.albumview.set_markup_column(1) self.albumview.set_column_spacing(0) self.albumview.set_spacing(0) self.albumview.set_item_width(100) self.albumview.set_property('activate-on-single-click', False) # Add a filter to the ListStore model self.albumfilter = self.albummodel.filter_new(None) self.remove_filter_data() self.matched = False def filter_visible(model, iter, data): usrf = self.widgets[1].get_object('searchentry').get_text() search_a = model.get_value(iter, 3) search_b = model.get_value(iter, 2) pre_result = False if self.matched: usrf_a = self.matched[0] usrf_b = self.matched[1] if usrf_b == 'blm.!ARTIST!': if usrf_a.lower() == search_b.lower(): # Matched an artist pre_result = True else: if (usrf_a.lower() == search_a.lower() and usrf_b.lower() == search_b.lower()): # Matched an album pre_result = True else: if len(model) > 0: if (usrf.lower() in search_a.lower() or usrf.lower() in search_b.lower()): # Found an element (artist or album name is close) pre_result = True else: # No element founded at all, return False anyway return False # Apply filters fdg = self.filter_data['genre'] fdy = self.filter_data['year'] # Filter results by genres if fdg is not None and fdg != model.get_value(iter, 5): pre_result = False # Filter results by years if fdy is not None and fdy != model.get_value(iter, 6): pre_result = False # Return the final result return pre_result self.albumfilter.set_visible_func(filter_visible) self.albumview.set_model(self.albumfilter) # Connect to signals def grab_entry_focus(widget, event): key = event.string if (key.lower() in 'a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,' 'x,y,z,0,1,2,3,4,5,6,7,8,9'.split(',')): self.widgets[1].get_object('searchentry').grab_focus() self.widgets[1].get_object('searchentry').set_text(key) self.widgets[1].get_object('searchentry').set_position(-1) self.albumview.connect('key-press-event', grab_entry_focus) self.albumview.connect('selection_changed', self.on_selection_changed) def populate_albums(self, albums_tree, albums, songs_tree, search): self.albums_tree = albums_tree self.albums = albums self.songs_tree = songs_tree self.search_entry = search.searchentry # Clear the tree model first self.albummodel.clear() # Show albums in the main explorer view self.album_nf = [] album_id = 0 for alb in self.albums_tree: bdir = join(self.userconf.datadir, 'modules', 'player', 'covers') album = alb['album'] artist = alb['artist'] cover = join(bdir, self.functions.get_hash(album, artist)) if isfile(cover): cover_px = Pixbuf.new_from_file_at_scale(cover, 150, 150, True) else: cover_px = Pixbuf.new_from_file(join(self.functions.datadir, 'image', 'logo_head_big.png')) self.albums[album_id] = Album(artist, album, self.songs_tree) ap = self.albummodel.append([cover_px, '<b>' + self.functions.view_encode(album) + '</b>\n<span foreground="grey">' + self.functions.view_encode(artist) + '</span>', artist, album, album_id, '', '']) album_id += 1 if not isfile(cover): self.album_nf.append([cover, ap, None]) # Check if we have to regenerate thumbnail (cover not found at startup) if len(self.album_nf) > 0: def regenerate_thumb(): new_album = [] for alb in self.album_nf: if isfile(alb[0]): item_iter = alb[1] cover_md5 = md5(open(alb[0], 'rb').read()).hexdigest() if alb[2] == None or alb[2] != cover_md5: cover_px = Pixbuf.new_from_file_at_scale(alb[0], 150, 150, True) self.albummodel.set_value(item_iter, 0, cover_px) alb[2] = cover_md5 new_album.append(alb) else: new_album.append(alb) if len(new_album) > 0: self.album_nf = new_album return True timeout_add(15000, regenerate_thumb) def on_album_matched(self, album): for item in self.albummodel: if item[2] == album.artist and item[3] == album.name: self.on_selection_changed(self.albumview, album) def on_selection_changed(self, icon_view, album=None): popup = Popover.new(self.albumview) popup.set_size_request(810, 240) if album is None: selection = icon_view.get_selected_items() if len(selection) != 1: return path = selection[0] treeiter = self.albumfilter.get_iter(path) isset, path, cell = icon_view.get_cursor() isset, rect = icon_view.get_cell_rect(path, cell) popup.set_pointing_to(rect) album_id = self.albumfilter.get_value(treeiter, 4) album_obj = self.albums[album_id] else: album_obj = album popup.set_relative_to(self.search_entry) # Handle double clicks def empty_dblclick(): self.dblclick = None if self.dblclick is None: self.dblclick = album_obj timeout_add(1000, empty_dblclick) elif self.dblclick == album_obj: self.play(album_obj) return album = album_obj.name artist = album_obj.artist glade_album = join(self.functions.datadir, 'glade', 'albumview.ui') box = gtk_builder() box.set_translation_domain('bluemindo') box.add_from_file(glade_album) popup.add(box.get_object('box1')) box.get_object('label_album').set_text(album) box.get_object('label_artist').set_text(artist) bdir = join(self.userconf.datadir, 'modules', 'player', 'covers') cover = join(bdir, self.functions.get_hash(album, artist)) if isfile(cover): cover_px = Pixbuf.new_from_file_at_scale(cover, 180, 180, True) else: cover_px = Pixbuf.new_from_file(join(self.functions.datadir, 'image', 'logo_head_big.png')) box.get_object('album_cover').set_from_pixbuf(cover_px) def play_album(wdg, album): self.play(album) def queue_album(wdg, album): self.queue(album) def change_cover(wdg, ka, album): artist_name = album.artist album_name = album.name fcdialog = FileChooserDialog( title=_('Change the cover picture for this album'), buttons=(_('Select'), ResponseType.OK)) fcdialog.set_transient_for(self.widgets[0][11]) response = fcdialog.run() if response == ResponseType.OK: filename = fcdialog.get_filename() datadir = self.userconf.datadir hash_a = self.functions.get_hash(album_name, artist_name) pictures_dir = join(datadir, 'modules', 'player', 'covers') album_file = join(pictures_dir, hash_a) copyfile(filename, album_file) new = Pixbuf.new_from_file_at_scale(album_file, 180, 180, True) box.get_object('album_cover').set_from_pixbuf(new) fcdialog.destroy() box.get_object('button_play').connect('clicked', play_album, album_obj) box.get_object('button_add').connect('clicked', queue_album, album_obj) box.get_object('coverevent').connect('button-press-event', change_cover, album_obj) i = 0 a = -1 previous_column = 0 grid_songs = box.get_object('grid_songs') grid_songs.set_size_request(-1, 200) grid_songs.set_column_spacing(5) try: kids = grid_songs.get_children() for kid in kids: grid_songs.remove(kid) except IndexError: pass for song in album_obj.tracks: i += 1 a += 1 def queue(wdg, song): self.queue(song) def play(wdg, song): self.play(song) song_wdg = Box(spacing=0) song_btr = Button() song_btr.connect('clicked', play, song) song_btr.set_relief(ReliefStyle.NONE) song_btr_content = Box(spacing=0) song_btr.add(song_btr_content) song_tr = Label() song_tr.set_markup('<span foreground="grey">' + str(song.track) + '</span>') song_tr.set_width_chars(3) song_btr_content.pack_start(song_tr, False, True, 0) song_ti = Label() song_ti.set_markup('<b>' + self.functions.view_encode(song.title, 22) + '</b>') song_ti.set_alignment(0.0, 0.5) song_ti.set_size_request(190, -1) song_btr_content.pack_start(song_ti, False, False, 0) length = self.functions.human_length(song.length) song_le = Label() song_le.set_markup('<span foreground="grey">' + length + '</span>') song_le.set_width_chars(5) song_btr_content.pack_start(song_le, False, True, 0) song_wdg.pack_start(song_btr, False, False, 0) song_add = Button.new_from_icon_name('list-add-symbolic', 0) song_add.set_property('relief', 2) song_add.set_size_request(14, 14) song_add.connect('clicked', queue, song) song_wdg.pack_start(song_add, False, False, 0) if i <= len(album_obj.tracks)/2: column = 0 previous_column = 0 row = a else: if previous_column == 0: a = 0 column = 1 previous_column = 1 row = a grid_songs.attach(song_wdg, column, row, 1, 1) popup.show_all() def play(self, usrobject): kind = usrobject.kind if kind == 'album': self.extensions.load_event('OnPlayNewAlbum', usrobject) else: self.extensions.load_event('OnPlayNewSong', usrobject) def queue(self, usrobject): kind = usrobject.kind if kind == 'album': self.extensions.load_event('OnAlbumQueued', usrobject) else: self.extensions.load_event('OnSongQueued', usrobject) def generate_filter_data(self, album_data): for element in self.albummodel: artist = element[2] album = element[3] datalb = album_data[artist][album] datalb_genre = datalb['genre'] datalb_year = datalb['year'] element[5] = datalb_genre element[6] = datalb_year def add_filter_data(self, value, field): self.filter_data[field] = value self.albumfilter.refilter() def remove_filter_data(self, cmb=None): if cmb is None: self.filter_data = {'genre': None, 'year': None} else: self.filter_data[cmb] = None self.albumfilter.refilter()