Exemple #1
0
    def __init__(self, pylast, settings):
        super().__init__(use_header_bar=1)
        self.set_title('Last.fm')
        self.set_default_size(300, -1)
        self.set_resizable(False)
        self.connect('delete-event', self.on_close)

        self.worker = GObjectWorker()
        self.settings = settings
        self.pylast = pylast
        self.auth_url = ''

        if self.settings['data']:
            self.auth_state = self.AuthState.AUTHORIZED
        else:
            self.auth_state = self.AuthState.NOT_AUTHORIZED

        self.label = Gtk.Label.new(None)
        self.label.set_halign(Gtk.Align.CENTER)
        self.button = Gtk.Button()
        self.button.set_halign(Gtk.Align.CENTER)
        self.set_widget_text()
        self.button.connect('clicked', self.on_clicked)

        content_area = self.get_content_area()
        content_area.add(self.label)
        content_area.add(self.button)
        content_area.show_all()
Exemple #2
0
    def on_prepare(self):
        try:
            import pylast
        except ImportError:
            logging.warning('pylast not found.')
            return _('pylast not found')

        self.pylast = pylast
        self.worker = GObjectWorker()
        self.preferences_dialog = LastFmAuth(self.pylast, self.settings)
        self.preferences_dialog.connect('lastfm-authorized',
                                        self.on_lastfm_authorized)
        self.window.prefs_dlg.connect('login-changed', self._show_dialog)
Exemple #3
0
    def __init__(self, pylast, settings):
        super().__init__(use_header_bar=1)
        self.set_title('Last.fm')
        self.set_default_size(300, -1)
        self.set_resizable(False)
        self.connect('delete-event', self.on_close)

        self.worker = GObjectWorker()
        self.settings = settings
        self.pylast = pylast
        self.auth_url = ''

        if self.settings['data']:
            self.auth_state = self.AuthState.AUTHORIZED
        else:
            self.auth_state = self.AuthState.NOT_AUTHORIZED

        self.label = Gtk.Label.new(None)
        self.label.set_halign(Gtk.Align.CENTER)
        self.button = Gtk.Button()
        self.button.set_halign(Gtk.Align.CENTER)
        self.set_widget_text()
        self.button.connect('clicked', self.on_clicked)

        content_area = self.get_content_area()
        content_area.add(self.label)
        content_area.add(self.button)
        content_area.show_all()
Exemple #4
0
 def on_prepare(self):
     try:
         import pylast
     except ImportError:
         logging.warning('pylast not found.')
         self.prepare_complete(error=_('pylast not found'))
     else:
         self.pylast = pylast
         self.worker = GObjectWorker()
         self.preferences_dialog = LastFmAuth(self.pylast, self.settings)
         self.preferences_dialog.connect('lastfm-authorized', self.on_lastfm_authorized)
         self.window.prefs_dlg.connect('login-changed', self._show_dialog)
         self.prepare_complete()
    def init(self):
        self.__activated = False
        self.__shell = self.props.shell
        self.__db = self.__shell.props.db
        self.__player = self.__shell.props.shell_player
        self.__plugin = self.props.plugin
        self.__entry_type = self.props.entry_type

        self.gconf = GConf.Client.get_default()
        self.vbox_main = None

        self.create_window()
        self.create_popups()

        # Pandora
        self.pandora = make_pandora()
        self.worker = GObjectWorker()

        self.stations_model = StationsModel(self.__db, self.__entry_type)
        self.songs_model = SongsModel(self.__db, self.__entry_type)
        self.songs_list.set_model(self.songs_model)
        self.props.query_model = self.songs_model # Enables skipping

        self.current_station = None
        self.current_song = None
        self.connected = False
        self.request_outstanding = False

        self.songs_action = SongsAction(self)
        self.stations_action = StationsAction(self, self.__plugin)

        self.notification_icon = NotificationIcon(self.__plugin, self.songs_action)
        self.refresh_notification_icon()

        self.connect_all()

        self.retrying = False
        self.waiting_for_playlist = False
Exemple #6
0
class LastfmPlugin(PithosPlugin):
    preference = 'enable_lastfm'
    description = _('Scrobble songs to Last.fm')

    is_really_enabled = False
    network = None

    def on_prepare(self):
        try:
            import pylast
        except ImportError:
            logging.warning('pylast not found.')
            return _('pylast not found')

        self.pylast = pylast
        self.worker = GObjectWorker()
        self.preferences_dialog = LastFmAuth(self.pylast, self.settings)
        self.preferences_dialog.connect('lastfm-authorized',
                                        self.on_lastfm_authorized)
        self.window.prefs_dlg.connect('login-changed', self._show_dialog)

    def on_enable(self):
        if self.settings['data']:
            self._enable_real()
        else:
            # Show the LastFmAuth dialog on enabling the plugin if we aren't aready authorized.
            dialog = self.preferences_dialog
            dialog.set_transient_for(self.window.prefs_dlg)
            dialog.set_destroy_with_parent(True)
            dialog.set_modal(True)
            dialog.show_all()

    def on_lastfm_authorized(self, prefs_dialog, auth_state):
        if auth_state is prefs_dialog.AuthState.AUTHORIZED:
            self._enable_real()

        elif auth_state is prefs_dialog.AuthState.NOT_AUTHORIZED:
            self.on_disable()

    def _show_dialog(self, *ignore):
        if not self.network or not self.settings['data']:
            return

        def err(e):
            logging.error(
                'Could not get Last.fm username. Error: {}'.format(e))
            return None

        def get_username():
            username = self.network.get_authenticated_user().get_name()
            logging.debug('Got Last.fm username: {}'.format(username))
            return username

        self.worker.send(get_username, (), self._dialog, err)

    def _dialog(self, username):
        if not username:
            return

        def on_response(dialog, response):
            if self.enabled:
                disable_response = Gtk.ResponseType.NO
            else:
                disable_response = Gtk.ResponseType.YES

            if response == disable_response:
                self.preferences_dialog.auth_state = self.preferences_dialog.AuthState.NOT_AUTHORIZED
                self.settings.reset('enabled')
                self.settings.reset('data')
                self.preferences_dialog.button.set_sensitive(True)
                self.preferences_dialog.set_widget_text()
                if self.enabled:
                    self.on_disable()

            dialog.destroy()

        if self.enabled:
            text = _('The Last.fm Plugin is Enabled')
            secondary_text = _(
                'Would you like to continue Scrobbling to this Last.fm account?'
            )
            trinary_text = _(
                'You will need to re-enable the Last.fm Plugin if you wish to Scrobble to a different account.'
            )
        else:
            text = _('The Last.fm Plugin is Disabled')
            secondary_text = _(
                'But Pithos is still authorized with this Last.fm account:')
            trinary_text = _('Would you like to deauthorize it?')

        if self.window.prefs_dlg.get_visible():
            parent = self.window.prefs_dlg
        else:
            parent = self.window

        dialog = Gtk.MessageDialog(
            parent=parent,
            flags=Gtk.DialogFlags.MODAL,
            type=Gtk.MessageType.INFO,
            buttons=Gtk.ButtonsType.YES_NO,
            text=text,
            secondary_text=secondary_text,
        )

        dialog.connect('response', on_response)

        link_label = Gtk.Label.new(None)
        link_label.set_halign(Gtk.Align.CENTER)
        link = 'https://www.last.fm/user/{}'.format(username)
        link_label.set_markup('<a href="{}">{}</a>'.format(link, username))
        trinary_label = Gtk.Label.new(trinary_text)
        trinary_label.set_halign(Gtk.Align.CENTER)

        message_area = dialog.get_message_area()
        message_area.add(link_label)
        message_area.add(trinary_label)

        message_area.show_all()
        dialog.show()

    def _enable_real(self):
        self._connect(self.settings['data'])
        self.is_really_enabled = True
        # Update Last.fm if plugin is enabled in the middle of a song.
        if self.window.current_song:
            self._on_song_changed(self.window, self.window.current_song)
        self._handlers = [
            self.window.connect('song-ended', self._on_song_ended),
            self.window.connect('song-changed', self._on_song_changed),
        ]
        logging.debug('Last.fm plugin fully enabled')

    def on_disable(self):
        if self.is_really_enabled:
            if self._handlers:
                for handler in self._handlers:
                    self.window.disconnect(handler)
            if self.preferences_dialog.auth_state is self.preferences_dialog.AuthState.AUTHORIZED:
                self._show_dialog()
        self.is_really_enabled = False
        self._handlers = []

    def _connect(self, session_key):
        # get_lastfm_network is deprecated. Use LastFMNetwork preferably.
        if hasattr(self.pylast, 'LastFMNetwork'):
            get_network = self.pylast.LastFMNetwork
        else:
            get_network = self.pylast.get_lastfm_network
        self.network = get_network(api_key=API_KEY,
                                   api_secret=API_SECRET,
                                   session_key=session_key)

    def _on_song_changed(self, window, song):
        def err(e):
            logging.error(
                'Failed to update Last.fm now playing. Error: {}'.format(e))

        def success(*ignore):
            logging.debug('Updated Last.fm now playing. {} by {}'.format(
                song.title, song.artist))

        self.worker.send(self.network.update_now_playing,
                         (song.artist, song.title, song.album), success, err)

    def _on_song_ended(self, window, song):
        def err(e):
            logging.error(
                'Failed to Scrobble song at Last.fm. Error: {}'.format(e))

        def success(*ignore):
            logging.info('Scrobbled {} by {} to Last.fm'.format(
                song.title, song.artist))

        duration = song.get_duration_sec()
        position = song.get_position_sec()
        if not song.is_ad and duration > 30 and (position > 240
                                                 or position > duration / 2):
            args = (
                song.artist,
                song.title,
                int(song.start_time),
                song.album,
                None,
                None,
                int(duration),
            )

            self.worker.send(self.network.scrobble, args, success, err)
Exemple #7
0
class LastFmAuth(Gtk.Dialog):
    __gtype_name__ = 'LastFmAuth'
    __gsignals__ = {
        'lastfm-authorized':
        (GObject.SignalFlags.RUN_FIRST, None, (GObject.TYPE_PYOBJECT, )),
    }

    class AuthState(Enum):
        NOT_AUTHORIZED = 0
        BEGAN_AUTHORIZATION = 1
        AUTHORIZED = 2

    def __init__(self, pylast, settings):
        super().__init__(use_header_bar=1)
        self.set_title('Last.fm')
        self.set_default_size(300, -1)
        self.set_resizable(False)
        self.connect('delete-event', self.on_close)

        self.worker = GObjectWorker()
        self.settings = settings
        self.pylast = pylast
        self.auth_url = ''

        if self.settings['data']:
            self.auth_state = self.AuthState.AUTHORIZED
        else:
            self.auth_state = self.AuthState.NOT_AUTHORIZED

        self.label = Gtk.Label.new(None)
        self.label.set_halign(Gtk.Align.CENTER)
        self.button = Gtk.Button()
        self.button.set_halign(Gtk.Align.CENTER)
        self.set_widget_text()
        self.button.connect('clicked', self.on_clicked)

        content_area = self.get_content_area()
        content_area.add(self.label)
        content_area.add(self.button)
        content_area.show_all()

    def on_close(self, *ignore):
        self.hide()
        # Don't let things be left in a half authorized state if the dialog is closed and not fully authorized.
        # Also disable the plugin if it's not fully authorized so there's no confusion.
        if self.auth_state is not self.AuthState.AUTHORIZED:
            self.auth_state = self.AuthState.NOT_AUTHORIZED
            self.settings.reset('enabled')
            self.button.set_sensitive(True)
            self.set_widget_text()
        return True

    def set_widget_text(self):
        if self.auth_state is self.AuthState.AUTHORIZED:
            self.button.set_label(_('Deauthorize'))
            self.label.set_text(_('Pithos is Authorized with Last.fm'))

        elif self.auth_state is self.AuthState.NOT_AUTHORIZED:
            self.button.set_label(_('Authorize'))
            self.label.set_text(_('Pithos is not Authorized with Last.fm'))

        elif self.auth_state is self.AuthState.BEGAN_AUTHORIZATION:
            self.button.set_label(_('Finish'))
            self.label.set_text(_('Click Finish when Authorized with Last.fm'))

    def setkey(self, key):
        if not key:
            self.auth_state = self.AuthState.NOT_AUTHORIZED
            self.settings.reset('data')
            logging.debug('Last.fm Auth Key cleared')

        else:
            self.auth_state = self.AuthState.AUTHORIZED
            self.settings['data'] = key
            logging.debug('Got Last.fm Auth Key: {}'.format(key))

        self.set_widget_text()
        self.button.set_sensitive(True)
        self.emit('lastfm-authorized', self.auth_state)

    def begin_authorization(self):
        def err(e):
            logging.error(
                'Failed to begin Last.fm authorization. Error: {}'.format(e))
            self.setkey('')

        def callback(url):
            self.auth_url = url
            logging.debug('Opening Last.fm Auth url: {}'.format(self.auth_url))
            open_browser(self.auth_url)
            self.button.set_sensitive(True)

        self.auth_state = self.AuthState.BEGAN_AUTHORIZATION
        # get_lastfm_network is deprecated. Use LastFMNetwork preferably.
        if hasattr(self.pylast, 'LastFMNetwork'):
            get_network = self.pylast.LastFMNetwork
        else:
            get_network = self.pylast.get_lastfm_network
        self.sg = self.pylast.SessionKeyGenerator(
            get_network(api_key=API_KEY, api_secret=API_SECRET))

        self.set_widget_text()
        self.button.set_sensitive(False)
        self.worker.send(self.sg.get_web_auth_url, (), callback, err)

    def finish_authorization(self):
        def err(e):
            logging.error(
                'Failed to finish Last.fm authorization. Error: {}'.format(e))
            self.setkey('')

        self.button.set_sensitive(False)
        self.worker.send(self.sg.get_web_auth_session_key, (self.auth_url, ),
                         self.setkey, err)

    def on_clicked(self, *ignore):
        if self.auth_state is self.AuthState.NOT_AUTHORIZED:
            self.begin_authorization()

        elif self.auth_state is self.AuthState.BEGAN_AUTHORIZATION:
            self.finish_authorization()

        elif self.auth_state is self.AuthState.AUTHORIZED:
            self.setkey('')
Exemple #8
0
def get_worker():
    # so it can be shared between the plugin and the authorizer
    global _worker
    if not _worker:
        _worker = GObjectWorker()
    return _worker
class PandoraSource(RB.StreamingSource):
#    __gproperties__ = {
#            'plugin': (rb.Plugin,
#            'plugin',
#            'plugin',
#            gobject.PARAM_WRITABLE | gobject.PARAM_CONSTRUCT_ONLY)
#    }

    def __init__(self):
        RB.StreamingSource.__init__(self,name="PandoraPlugin")

    def init(self):
        self.__activated = False
        self.__shell = self.props.shell
        self.__db = self.__shell.props.db
        self.__player = self.__shell.props.shell_player
        self.__plugin = self.props.plugin
        self.__entry_type = self.props.entry_type

        self.gconf = GConf.Client.get_default()
        self.vbox_main = None

        self.create_window()
        self.create_popups()

        # Pandora
        self.pandora = make_pandora()
        self.worker = GObjectWorker()

        self.stations_model = StationsModel(self.__db, self.__entry_type)
        self.songs_model = SongsModel(self.__db, self.__entry_type)
        self.songs_list.set_model(self.songs_model)
        self.props.query_model = self.songs_model # Enables skipping

        self.current_station = None
        self.current_song = None
        self.connected = False
        self.request_outstanding = False

        self.songs_action = SongsAction(self)
        self.stations_action = StationsAction(self, self.__plugin)

        self.notification_icon = NotificationIcon(self.__plugin, self.songs_action)
        self.refresh_notification_icon()

        self.connect_all()

        self.retrying = False
        self.waiting_for_playlist = False

    def refresh_notification_icon(self, icon_enabled = None):
        if icon_enabled:
            enabled = icon_enabled
        else:
            enabled = self.gconf.get_bool(GCONF_KEYS['icon'])

        if enabled and self.__player.get_playing_source() == self:
            self.notification_icon.show()
        else:
            self.notification_icon.hide()

    def do_selected(self):
        print("do_selected")

    def do_get_status(self, *args):
        """Return the current status of the Pandora source.

        Called by Rhythmbox to figure out what to show on the statusbar.

        Args:
            args: Unknown

        Returns:
            A tuple of:
                the text status to display
                the status to display on the progress bar
                the progress value, if the progress value is less than zero, the progress bar will pulse.
        """
        progress_text = None
        progress = 1
        text = ""

        if self.connected:
            self.error_area.hide()
            num_stations = self.stations_model.get_num_entries()
            if num_stations > 1:
                text =  str(num_stations) + " stations"
            else:
                text =  str(num_stations) + " station"
        else:
            text = "Not connected to Pandora"

        # Display Current Station Info
        if self.__player.get_playing() and self.__player.get_playing_source() == self:
            station_name = self.current_station.name
            text = "Playing " + station_name + ", " + text
        if self.request_outstanding:
            progress_text = self.request_description
            progress = -1
        return (text, progress_text, progress)

    def do_impl_get_entry_view(self):
        print("do_impl_get_entry_view")
        return self.songs_list

    def do_impl_can_pause(self):
        """Indicate that pausing of source entries is available.

        Called by Rhythmbox to determine whether playback of entries from the source can be paused.

        Returns:
            True to indicate the pausing is supported.
        """
        return True

    def do_impl_handle_eos(self):
        """Handle End Of Stream event by going to the next song.

        Called by Rhythmbox to handle EOS events when playing entries from this source.

        Returns:
            The source EOF type indicating to skip to the next song.
        """
        return RB.SourceEOFType(3) # next

    def do_impl_try_playlist(self):
        """Don't try to parse playback URIs in this source as playlists.

        Called by Rhythmbox to determine if URIs should be parsed as playlists rather than just played.

        Returns:
            False to disable playlist parsing.
        """
        return False

    def do_songs_show_popup(self, view, over_entry):
        if over_entry:
            selected = view.get_selected_entries()
            if len(selected) == 1:
                self.show_single_popup("/PandoraSongViewPopup")

    def do_stations_show_popup(self, view, over_entry):
        if over_entry:
            selected = view.get_selected_entries()
            if len(selected) == 1:
                self.show_single_popup("/PandoraStationViewPopup")
        else:
            self.show_single_popup("/PandoraSourceMainPopup")

    def show_single_popup(self, popup):
        popup = self.__player.props.ui_manager.get_widget(popup)
        popup.popup(None, None, None, None, 3, Gtk.get_current_event_time())

    def playing_source_changed(self, source):
        print("Playing source changed")
        if not self.notification_icon:
            return
        if self != source:
            self.notification_icon.hide()
        else:
            self.notification_icon.show()

    def playing_entry_changed(self, entry):
        print("Playing Entry changed")
        if not self.__db or not entry:
            return
        if entry.get_entry_type() != self.__entry_type:
            return
        self.get_metadata(entry)

        url = entry.get_playback_uri()

        self.current_song = self.songs_model.get_song(url)
        if self.songs_model.is_last_entry(entry):
            self.get_playlist()

    def create_window(self):
        if self.vbox_main:
            self.remove(self.vbox_main)
            self.vbox_main.hide_all()
            self.vbox_main = None

        self.stations_list = widgets.StationEntryView(self.__db, self.__player)
        self.songs_list = widgets.SongEntryView(self.__db, self.__player, self.__plugin)

        self.vbox_main = Gtk.VBox(False, 5)

        paned = Gtk.VPaned()
        frame1 = Gtk.Frame()
        frame1.set_shadow_type(Gtk.ShadowType.OUT)
        frame1.add(self.stations_list)
        paned.pack1(frame1, True, False)
        frame2 = Gtk.Frame()
        frame2.set_shadow_type(Gtk.ShadowType.OUT)
        frame2.add(self.songs_list)
        paned.pack2(frame2, True, False)
        self.vbox_main.pack_start(paned, True, True, 0)

        self.error_area = widgets.ErrorView(self.__plugin, self.do_impl_activate)
        error_frame = self.error_area.get_error_frame()
        self.vbox_main.pack_end(error_frame, False, False, 0)

        self.vbox_main.show_all()
        self.error_area.hide()
        self.add(self.vbox_main)


    def connect_all(self):
        self.stations_list.connect('show_popup', self.do_stations_show_popup)
        self.songs_list.connect('show_popup', self.do_songs_show_popup)
        self.songs_action.connect()
        self.stations_action.connect()


    def create_popups(self):
        self.action_group = Gtk.ActionGroup('PandoraPluginActions')
        action = Gtk.Action('SongInfo', _('Song Info...'), _('View song information in browser'), 'gtk-info')
        self.action_group.add_action(action)
        action = Gtk.Action('LoveSong', _('Love Song'), _('I love this song'), 'gtk-about')
        self.action_group.add_action(action)
        action = Gtk.Action('BanSong', _('Ban Song'), _("I don't like this song"), 'gtk-cancel')
        self.action_group.add_action(action)
        action = Gtk.Action('TiredSong', _('Tired of this song'), _("I'm tired of this song"), 'gtk-jump-to')
        self.action_group.add_action(action)
        action = Gtk.Action('Bookmark', _('Bookmark'), _('Bookmark...'), 'gtk-add')
        self.action_group.add_action(action)
        action = Gtk.Action('BookmarkSong', _('Song'), _("Bookmark this song"), None)
        self.action_group.add_action(action)
        action = Gtk.Action('BookmarkArtist', _('Artist'), _("Bookmark this artist"), None)
        self.action_group.add_action(action)

        action = Gtk.Action('AddStation', _('Create a New Station'), _('Create a new Pandora station'), 'gtk-add')
        self.action_group.add_action(action)

        action = Gtk.Action('StationInfo', _('Station Info...'), _('View station information in browser'), 'gtk-info')
        self.action_group.add_action(action)
        action = Gtk.Action('DeleteStation', _('Delete this Station'), _('Delete this Pandora Station'), 'gtk-remove')
        self.action_group.add_action(action)

        # TODO: figure out how this should be done now.
        #manager = self.__player.props.ui_manager
        #manager.insert_action_group(self.action_group, 0)
        #popup_file = rb.find_plugin_file(self.__plugin, "pandora/pandora-ui.xml")
        #self.ui_id = manager.add_ui_from_file(popup_file)
        #manager.ensure_update()

     # rhyhtmbox api break up (0.13.2 - 0.13.3)
    def do_selected(self):
        print("Activating source.")
        if not self.__activated:
            try:
                self.username, self.password = self.get_pandora_account_info()
            except AccountNotSetException as instance:
                # Ask user to configure account
                self.error_area.show(instance.parameter)
                # Retry after the user has put in account information.
                return

            self.pandora_connect()

            self.__activated = True

    def do_impl_activate(self):
        self.do_selected()

    # TODO: Update UI and Error Msg
    def worker_run(self, fn, args=(), callback=None, message=None, context='net'):
        print("worker_run")
        self.request_outstanding = True
        self.request_description = message
        self.notify_status_changed()

        if isinstance(fn,str):
            fn = getattr(self.pandora, fn)

        def cb(v):
            self.request_outstanding = False
            self.request_description = None
            self.notify_status_changed()
            if callback: callback(v)

        def eb(e):

            def retry_cb():
                self.retrying = False
                if fn is not self.pandora.connect:
                    self.worker_run(fn, args, callback, message, context)

            self.request_outstanding = False
            self.request_description = None
            self.waiting_for_playlist = False
            self.connected = False
            self.notify_status_changed()

            if isinstance(e, PandoraAuthTokenInvalid) and not self.retrying:
                self.retrying = True
                print("Automatic reconnect after invalid auth token")
                self.pandora_connect("Reconnecting...", retry_cb)
            elif isinstance(e, PandoraNetError):
                error_message = "Unable to connect. Check your Internet connection."
                detail = e.message
                self.__activated = False
                self.error_area.show(error_message, detail)
                print(e.message)
            elif isinstance(e, PandoraError):
                error_message = e.message
                self.__activated = False
                self.error_area.show(error_message)
                print("Pandora returned error: ", e.message)
            else:
                print(e.traceback)

        self.worker.send(fn, args, cb, eb)

    def get_pandora_account_info(self):
        print("Getting account details...")

        error_message = "Account details are needed before you can connect.  Check your settings."

        attrs = GnomeKeyring.Attribute.list_new()
        GnomeKeyring.Attribute.list_append_string(attrs, 'rhythmbox-plugin', 'pandora')
        result, items = GnomeKeyring.find_items_sync(GnomeKeyring.ItemType.GENERIC_SECRET, attrs)

        if result == GnomeKeyring.Result.NO_MATCH or len(items) == 0:
            print("Pandora Account Info not found.")
            raise AccountNotSetException(error_message)

        secret = items[0].secret
        if secret is "":
            print("Pandora Account Info empty.")
            raise AccountNotSetException(error_message)
        return tuple(secret.split('\n'))


    def pandora_connect(self, message="Logging in...", callback=None):
        from pithos.pandora.data import client_keys, default_client_id
        args = (client_keys[default_client_id], self.username, self.password)

        def pandora_ready(*ignore):
            print("Pandora connected.")
            self.stations_model.clear()

            #TODO: Station already exists
            #FIXME: Leave out QuickMix for now
            #for i in self.pandora.stations:
            #    if i.isQuickMix:
            #        self.stations_model.add_station(i, "QuickMix")

            for station in self.pandora.stations:
                if not station.isQuickMix:
                    self.stations_model.add_station(station, station.name)
            self.stations_list.set_model(self.stations_model)
            self.connected = True
            if callback:
                callback()

        self.worker_run('connect', args, pandora_ready, message, 'login')

    def play_station(self, station_entry):
        prev_station = self.current_station

        url = station_entry.get_playback_uri()
        self.current_station = self.stations_model.get_station(url)

        if prev_station != None and prev_station != self.current_station:
            self.songs_model.clear()

        self.get_playlist(start = True)
        print("Station activated %s" %(url))

        now = int(time.time())
        self.__db.entry_set(station_entry, RB.RhythmDBPropType.LAST_PLAYED, now)
        self.__db.commit()

    def get_playlist(self, start = False):
        print("Getting playlist")

        if self.waiting_for_playlist: return

        def callback(songs):
            print("Got playlist")
            first_entry = None
            for song in songs:
                # TODO: set this properly rather than per song.
                song.pandora.set_audio_quality('mediumQuality')
                entry = self.songs_model.add_song(song)
                if first_entry == None:
                    first_entry = entry
                print("Title: %s" %(song.title))
                print("URL: %s" %(song.audioUrl))
            self.waiting_for_playlist = False
            if start:
                self.songs_list.scroll_to_entry(first_entry)
                self.__player.play_entry(first_entry, self)

        self.waiting_for_playlist = True
        self.worker_run(self.current_station.get_playlist, (), callback, "Getting songs...")

    def get_metadata(self, entry):
        if entry == None:
            print("Track not found in DB")
            return True
        duration = Gst.CLOCK_TIME_NONE
        try:
            sleep(1) # FIXME: Hack, but seems to be returning 0 immediately after the playing-song-changed signal
            query_success, format, duration_nanos = self.__player.props.player.props.playbin.query_duration(Gst.Format.TIME)
            if query_success and duration_nanos != Gst.CLOCK_TIME_NONE:
                duration_seconds = duration_nanos / 1000000000
                self.__db.entry_set(entry, RB.RhythmDBPropType.DURATION, duration_seconds)
                self.__db.commit()

        except Exception as e:
            print("Could not query duration")
            print(e)
            pass

    def get_progress(text, progress):
        print("get_progress")

    def get_current_song_entry(self):
        return self.__player.get_playing_entry()

    def next_song(self):
        print("next_song")
        self.__player.do_next()

    def is_current_song(self, song):
        return song is self.current_song

    def is_current_station(self, station):
        return station is self.current_station
Exemple #10
0
class LastfmPlugin(PithosPlugin):
    preference = 'enable_lastfm'
    description = _('Scrobble songs to Last.fm')

    is_really_enabled = False
    network = None

    def on_prepare(self):
        try:
            import pylast
        except ImportError:
            logging.warning('pylast not found.')
            self.prepare_complete(error=_('pylast not found'))
        else:
            self.pylast = pylast
            self.worker = GObjectWorker()
            self.preferences_dialog = LastFmAuth(self.pylast, self.settings)
            self.preferences_dialog.connect('lastfm-authorized', self.on_lastfm_authorized)
            self.window.prefs_dlg.connect('login-changed', self._show_dialog)
            self.prepare_complete()

    def on_enable(self):
        if self.settings['data']:
            self._enable_real()
        else:
            # Show the LastFmAuth dialog on enabling the plugin if we aren't aready authorized.
            dialog = self.preferences_dialog
            dialog.set_transient_for(self.window.prefs_dlg)
            dialog.set_destroy_with_parent(True)
            dialog.set_modal(True)
            dialog.show_all()

    def on_lastfm_authorized(self, prefs_dialog, auth_state):
        if auth_state is prefs_dialog.AuthState.AUTHORIZED:
            self._enable_real()

        elif auth_state is prefs_dialog.AuthState.NOT_AUTHORIZED:
            self.on_disable()

    def _show_dialog(self, *ignore):
        if not self.network or not self.settings['data']:
            return

        def err(e):
            logging.error('Could not get Last.fm username. Error: {}'.format(e))
            return None

        def get_username():
            username = self.network.get_authenticated_user().get_name()
            logging.debug('Got Last.fm username: {}'.format(username))
            return username

        self.worker.send(get_username, (), self._dialog, err)

    def _dialog(self, username):
        if not username:
            return

        def on_response(dialog, response):
            if self.enabled:
                disable_response = Gtk.ResponseType.NO
            else:
                disable_response = Gtk.ResponseType.YES

            if response == disable_response:
                self.preferences_dialog.auth_state = self.preferences_dialog.AuthState.NOT_AUTHORIZED
                self.settings.reset('enabled')
                self.settings.reset('data')
                self.preferences_dialog.button.set_sensitive(True)
                self.preferences_dialog.set_widget_text()
                if self.enabled:
                    self.on_disable()

            dialog.destroy()

        if self.enabled:
            text = _('The Last.fm Plugin is Enabled')
            secondary_text = _('Would you like to continue Scrobbling to this Last.fm account?')
            trinary_text = _(
                'You will need to re-enable the Last.fm Plugin if you wish to Scrobble to a different account.'
            )
        else:
            text = _('The Last.fm Plugin is Disabled')
            secondary_text = _('But Pithos is still authorized with this Last.fm account:')
            trinary_text = _('Would you like to deauthorize it?')

        if self.window.prefs_dlg.get_visible():
            parent = self.window.prefs_dlg
        else:
            parent = self.window

        dialog = Gtk.MessageDialog(
            parent=parent,
            flags=Gtk.DialogFlags.MODAL,
            type=Gtk.MessageType.INFO,
            buttons=Gtk.ButtonsType.YES_NO,
            text=text,
            secondary_text=secondary_text,
        )

        dialog.connect('response', on_response)

        link_label = Gtk.Label.new(None)
        link_label.set_halign(Gtk.Align.CENTER)
        link = 'https://www.last.fm/user/{}'.format(username)
        link_label.set_markup('<a href="{}">{}</a>'.format(link, username))
        trinary_label = Gtk.Label.new(trinary_text)
        trinary_label.set_halign(Gtk.Align.CENTER)

        message_area = dialog.get_message_area()
        message_area.add(link_label)
        message_area.add(trinary_label)

        message_area.show_all()
        dialog.show()

    def _enable_real(self):
        self._connect(self.settings['data'])
        self.is_really_enabled = True
        # Update Last.fm if plugin is enabled in the middle of a song.
        if self.window.current_song:
            self._on_song_changed(self.window, self.window.current_song)
        self._handlers = [
            self.window.connect('song-ended', self._on_song_ended),
            self.window.connect('song-changed', self._on_song_changed),
        ]
        logging.debug('Last.fm plugin fully enabled')

    def on_disable(self):
        if self.is_really_enabled:
            if self._handlers:
                for handler in self._handlers:
                    self.window.disconnect(handler)
            if self.preferences_dialog.auth_state is self.preferences_dialog.AuthState.AUTHORIZED:
                self._show_dialog()
        self.is_really_enabled = False
        self._handlers = []

    def _connect(self, session_key):
        # get_lastfm_network is deprecated. Use LastFMNetwork preferably.
        if hasattr(self.pylast, 'LastFMNetwork'):
            get_network = self.pylast.LastFMNetwork
        else:
            get_network = self.pylast.get_lastfm_network
        self.network = get_network(
            api_key=API_KEY, api_secret=API_SECRET,
            session_key=session_key
        )

    def _on_song_changed(self, window, song):
        def err(e):
            logging.error('Failed to update Last.fm now playing. Error: {}'.format(e))

        def success(*ignore):
            logging.debug('Updated Last.fm now playing. {} by {}'.format(song.title, song.artist))

        self.worker.send(self.network.update_now_playing, (song.artist, song.title, song.album), success, err)

    def _on_song_ended(self, window, song):
        def err(e):
            logging.error('Failed to Scrobble song at Last.fm. Error: {}'.format(e))

        def success(*ignore):
            logging.info('Scrobbled {} by {} to Last.fm'.format(song.title, song.artist))

        duration = song.get_duration_sec()
        position = song.get_position_sec()
        if not song.is_ad and duration > 30 and (position > 240 or position > duration / 2):
            args = (
                song.artist,
                song.title,
                int(song.start_time),
                song.album,
                None,
                None,
                int(duration),
            )

            self.worker.send(self.network.scrobble, args, success, err)
Exemple #11
0
class LastFmAuth(Gtk.Dialog):
    __gtype_name__ = 'LastFmAuth'
    __gsignals__ = {
        'lastfm-authorized': (GObject.SignalFlags.RUN_FIRST, None, (GObject.TYPE_PYOBJECT,)),
    }

    class AuthState(Enum):
        NOT_AUTHORIZED = 0
        BEGAN_AUTHORIZATION = 1
        AUTHORIZED = 2

    def __init__(self, pylast, settings):
        super().__init__(use_header_bar=1)
        self.set_title('Last.fm')
        self.set_default_size(300, -1)
        self.set_resizable(False)
        self.connect('delete-event', self.on_close)

        self.worker = GObjectWorker()
        self.settings = settings
        self.pylast = pylast
        self.auth_url = ''

        if self.settings['data']:
            self.auth_state = self.AuthState.AUTHORIZED
        else:
            self.auth_state = self.AuthState.NOT_AUTHORIZED

        self.label = Gtk.Label.new(None)
        self.label.set_halign(Gtk.Align.CENTER)
        self.button = Gtk.Button()
        self.button.set_halign(Gtk.Align.CENTER)
        self.set_widget_text()
        self.button.connect('clicked', self.on_clicked)

        content_area = self.get_content_area()
        content_area.add(self.label)
        content_area.add(self.button)
        content_area.show_all()

    def on_close(self, *ignore):
        self.hide()
        # Don't let things be left in a half authorized state if the dialog is closed and not fully authorized.
        # Also disable the plugin if it's not fully authorized so there's no confusion.
        if self.auth_state is not self.AuthState.AUTHORIZED:
            self.auth_state = self.AuthState.NOT_AUTHORIZED
            self.settings.reset('enabled')
            self.button.set_sensitive(True)
            self.set_widget_text()
        return True

    def set_widget_text(self):
        if self.auth_state is self.AuthState.AUTHORIZED:
            self.button.set_label(_('Deauthorize'))
            self.label.set_text(_('Pithos is Authorized with Last.fm'))

        elif self.auth_state is self.AuthState.NOT_AUTHORIZED:
            self.button.set_label(_('Authorize'))
            self.label.set_text(_('Pithos is not Authorized with Last.fm'))

        elif self.auth_state is self.AuthState.BEGAN_AUTHORIZATION:
            self.button.set_label(_('Finish'))
            self.label.set_text(_('Click Finish when Authorized with Last.fm'))

    def setkey(self, key):
        if not key:
            self.auth_state = self.AuthState.NOT_AUTHORIZED
            self.settings.reset('data')
            logging.debug('Last.fm Auth Key cleared')

        else:
            self.auth_state = self.AuthState.AUTHORIZED
            self.settings['data'] = key
            logging.debug('Got Last.fm Auth Key: {}'.format(key))

        self.set_widget_text()
        self.button.set_sensitive(True)
        self.emit('lastfm-authorized', self.auth_state)

    def begin_authorization(self):
        def err(e):
            logging.error('Failed to begin Last.fm authorization. Error: {}'.format(e))
            self.setkey('')

        def callback(url):
            self.auth_url = url
            logging.debug('Opening Last.fm Auth url: {}'.format(self.auth_url))
            open_browser(self.auth_url)
            self.button.set_sensitive(True)

        self.auth_state = self.AuthState.BEGAN_AUTHORIZATION
        # get_lastfm_network is deprecated. Use LastFMNetwork preferably.
        if hasattr(self.pylast, 'LastFMNetwork'):
            get_network = self.pylast.LastFMNetwork
        else:
            get_network = self.pylast.get_lastfm_network
        self.sg = self.pylast.SessionKeyGenerator(get_network(api_key=API_KEY, api_secret=API_SECRET))

        self.set_widget_text()
        self.button.set_sensitive(False)
        self.worker.send(self.sg.get_web_auth_url, (), callback, err)

    def finish_authorization(self):
        def err(e):
            logging.error('Failed to finish Last.fm authorization. Error: {}'.format(e))
            self.setkey('')

        self.button.set_sensitive(False)
        self.worker.send(self.sg.get_web_auth_session_key, (self.auth_url,), self.setkey, err)

    def on_clicked(self, *ignore):
        if self.auth_state is self.AuthState.NOT_AUTHORIZED:
            self.begin_authorization()

        elif self.auth_state is self.AuthState.BEGAN_AUTHORIZATION:
            self.finish_authorization()

        elif self.auth_state is self.AuthState.AUTHORIZED:
            self.setkey('')