Example #1
0
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()