Exemple #1
0
    def _update_status_map(self):
        for acct in self.status_map.keys():
            self.status_map[acct] = ('offline', None)
            self.log.verbose(_('Updating status for account %s') % acct)

            # Determine current status
            status = self.gajim_remote('get_status', [acct])
            if status in [False, None]:
                return
            status = status.strip()

            if not status:
                self.log.verbose(_('Gajim and/or Dbus is not running'))
                continue

            self.status_map[acct] = (status, None)
            if status not in UPDATE_STATUS_STATES:
                continue

            # Load current status message
            status_msg = self.gajim_remote('get_status_message', [acct])
            if not status_msg:
                continue

            status_msg = '%(now_playing)s'
            self.status_map[acct] = (status, status_msg)
Exemple #2
0
    def _handshake(self):
        # Update user/passwd from config if necessary
        if not self.user or not self.passwd:
            self.user = mesk.config.get(CONFIG_SECTION, 'username', '')
            self.passwd = mesk.config.get(CONFIG_SECTION, 'password', '')
            self.md5_passwd = self.get_md5(self.passwd)
            if not self.user or not self.passwd:
                self.log.critical('Add a username and password to the '
                                  'audioscrobbler plugin preferences.')
                self._update_status(state=_('No username and/or password'))
                return

        # Open handshake URL
        hs_url = HANDSHAKE_URL % (PROTOCOL_VERSION, APP, VERSION, self.user)
        url_data = None
        try:
            self.log.debug('Handshake: %s' % hs_url)
            url_data = urllib2.urlopen(hs_url)
        except Exception, ex:
            retry = 10 # minutes
            self.log.warning('Handshake error, retry in %d minutes: %s' %
                             (retry, str(ex)))
            self._update_status(state=_('Cannot connect to Last.FM'))
            # Fatal failure, retry in 10 minutes
            self.handshake(retry * 60)
            return
Exemple #3
0
    def run(self):
        import mesk
        self._init()

        if not mesk.info.DISABLE_DBUS_SUPPORT:
            mesk.log.debug('Testing for existing instance; profile \'%s\'' %
                           self.profile)
            import dbus_service
            mesk_running = dbus_service.is_service_running(self.profile)
            if mesk_running and not self.remote_opts:
                mesk.log.debug('mesk is running but no remote commands')
                raise mesk.MeskException(
                    _('Mesk is already running'),
                    _('Another instance of Mesk is using the profile \'%s\'. '
                      'Try again with a different profile name '
                      '(-p/--profile)') % self.profile)
            elif mesk_running:
                mesk.log.debug('mesk is running, execting remote commands')
                # Mesk is running and we have some remote control options, 
                # so execute the commands on the remote service and exit
                import dbus
                import dbus_service  # This is a local module
                obj_path = dbus_service.get_object_path(self.profile)
                service = dbus_service.get_service_name(self.profile)
                session_bus = dbus.SessionBus()
                obj = session_bus.get_object(service, obj_path)

                mesk_dbus = dbus.Interface(obj, dbus_service.INTERFACE)
                self._run_remote_commands(mesk_dbus)
                return
        elif self.remote_opts:
            raise RemoteControlException('Remote control disabled')


        # Gnome session management
        try:
            import gnome.ui
        except ImportError:
            mesk.log.info('Session management disabled (gnome.ui not found)')
        else:
            mesk.log.debug('Enabling Gnome session management')
            gnome.program_init(mesk.info.APP_NAME.lower(),
                               mesk.info.APP_VERSION)
            cli = gnome.ui.master_client()
            cli.connect('die', lambda: gtk.main_quit())
            argv = ['mesk'] + sys.argv[1:]
            try:
                cli.set_restart_command(argv)
            except TypeError:
                # Fedora systems have a broken gnome-python wrapper for this
                # function.  Credits to Quod Libet:
                # http://www.sacredchao.net/quodlibet/ticket/591
                cli.set_restart_command(len(argv), argv)

        # Initialize DB
        try:
            import mesk.database
        except Exception, ex:
            raise mesk.MeskException('Music Library Error',
                                     traceback.format_exc())
Exemple #4
0
 def _submit_post(self, post_data):
     try:
         self.log.debug('Submitting to %s: %s' % (self.submit_url,
                                                  post_data))
         url_data = urllib2.urlopen(self.submit_url, post_data)
     except Exception, ex:
         self.log.warning(_('Submit error: %s') % str(ex))
         self._update_status(state=_('Cannot connect to Last.FM'))
         return False
Exemple #5
0
 def deactivate_plugin(self, plugin_name):
     if plugin_name in self.__active_plugins:
         mesk.log.info(_('Deactivating %s plugin') % plugin_name)
         plugin = self.__active_plugins[plugin_name]
         del self.__active_plugins[plugin_name]
         try:
             plugin.shutdown()
         except Exception, ex:
             mesk.log.warning(_('Error deactivating %s plugin: %s') % \
                              (plugin_name, str(ex)))
             raise
         self.update_active_config()
         return True
Exemple #6
0
    def __init__(self, glade_xml, window):
        self.xml = glade_xml
        self.window = window
        self.xml.signal_autoconnect(self)

        self.xml.get_widget('remove_libdir_button').set_sensitive(False)
        self._last_lib_add_dir = None
        self.xml.get_widget('remove_excludedir_button').set_sensitive(False)
        self._last_lib_exclude_dir = None

        self.MODEL_PATH = 0

        def init_path_view(view_name, col_title, dirs):
            model = gtk.ListStore(str)

            view = self.xml.get_widget(view_name)
            view.set_model(model)

            col = gtk.TreeViewColumn(col_title)
            txt_renderer = gtk.CellRendererText()
            col.pack_start(txt_renderer, True)
            col.add_attribute(txt_renderer, 'text', self.MODEL_PATH)
            view.append_column(col)

            # Populate paths
            for d in dirs:
                model.append([d])

            return view

        self.libdir_view = init_path_view('libdirs_treeview',
                                          _('Library Paths'),
                                          mesk.database.db.get_lib_dirs())
        self.excludedir_view = \
                init_path_view('excludedirs_treeview',
                               _('Exclude Paths'),
                               mesk.database.db.get_lib_exclude_dirs())

        self.sync_button = self.xml.get_widget('sync_button')
        have_one_dir = self.libdir_view.get_model().get_iter_first(
        ) is not None
        self.sync_button.set_sensitive(have_one_dir)

        self.sync_stop_button = self.xml.get_widget('sync_stop_button')
        self.sync_stop_button.set_sensitive(False)

        self.sync_progressbar = self.xml.get_widget('sync_progressbar')
        self.sync_status_label = self.xml.get_widget('sync_status_label')
Exemple #7
0
    def _add_path_helper(self, start_dir, model):
        parent_dir = None

        d = gtk.FileChooserDialog(title=_('Select Directory'),
                                  parent=self.window,
                                  action=gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER,
                                  buttons=(gtk.STOCK_CANCEL,
                                           gtk.RESPONSE_CANCEL, gtk.STOCK_ADD,
                                           gtk.RESPONSE_OK))
        d.set_select_multiple(True)
        d.set_current_folder(os.path.expandvars(start_dir))
        resp = d.run()
        if resp == gtk.RESPONSE_OK:
            # Note, these URIs should already be escaped
            for uri in [mesk.uri.make_uri(u) for u in d.get_uris()]:
                path = mesk.uri.unescape(uri.path)
                model.append([path])
                if parent_dir is None:
                    parent_dir = path
                else:
                    # Multiple selections from within a dir
                    parent_dir = os.path.dirname(path)
            self._save_dir_state()

        d.destroy()
        return parent_dir
Exemple #8
0
 def activate_plugin(self, plugin_name):
     if plugin_name not in self.__active_plugins:
         try:
             plugin_info = self.__all_plugins[plugin_name]
             mesk.log.verbose(_('Activating %s plugin') % plugin_name)
             plugin = plugin_info['CLASS']()
             self.__active_plugins[plugin_name] = plugin
         except KeyError:
             mesk.log.warning(_('Invalid plugin name: %s') % plugin_name)
             return False
         except Exception, ex:
             mesk.log.warning(_('Error activating %s plugin: %s') % \
                              (plugin_name, str(ex)))
             raise
         self.update_active_config()
         return True
Exemple #9
0
    def _on_ok_button_clicked(self, button):
        def append_extension(name, ext):
            '''Add extension if none exists'''
            if not os.path.splitext(name)[1]:
                if ext[0] != '.':
                    name += '.'
                name += ext
            return name

        name = self._curr_filename
        directory = self.dir_chooser.get_filename()

        name = append_extension(name, self._curr_ext)
        if os.path.exists(os.path.join(directory, name)):
            # Confirm file overwrites
            from dialogs import ConfirmationDialog
            d = ConfirmationDialog(None, type=gtk.MESSAGE_WARNING)
            d.set_markup(_('Overwrite existing file \'%s\'?') %
                         os.path.join(directory, name))
            if not d.confirm():
                return

        self._thread = threading.Thread(target=self._export_thread,
                                        args=(directory, name))
        self._thread.start()
Exemple #10
0
 def _show_warn_dialog(self, uri):
     from dialogs import WarningDialog
     d = WarningDialog(None)
     msg = (_("Playlist entry '%s' will be omitted from the archive since "
              "it is not a local file") % mesk.uri.unescape(str(uri)))
     d.set_markup(msg)
     d.run()
     d.destroy()
Exemple #11
0
 def plugin_view_menu_items(self):
     item = gtk.ImageMenuItem(_('Lyrics Search'))
     item.set_image(
         gtk.image_new_from_stock(gtk.STOCK_JUMP_TO, gtk.ICON_SIZE_MENU))
     item.connect('activate', self._on_search_menu_activate)
     if not self._current_src:
         item.set_sensitive(False)
     return [item]
Exemple #12
0
 def __init__(self, parent, modal=True, type=gtk.MESSAGE_QUESTION):
     ConfirmationDialog.__init__(self,
                                 parent=parent,
                                 modal=modal,
                                 type=type)
     self.checkbutton = gtk.CheckButton(_('Do not ask me again.'))
     self.vbox.pack_start(self.checkbutton, expand=False, fill=False)
     self.checkbutton.show()
Exemple #13
0
 def _update_export_label(self):
     name = ''
     if self._curr_filename:
         name = ': %s' % self._curr_filename
         if not os.path.splitext(name)[1]:
             # No extension, add the default
             name += self._curr_ext
     self.export_label.set_markup(_('Export %s%s') %
                                  (self._curr_display_name, name))
Exemple #14
0
            def show_error():
                import traceback
                msg = _('Playlist export failed: %s') % str(ex)
                mesk.log.error("%s\n%s" % (msg, traceback.format_exc()))

                from dialogs import ErrorDialog
                d = ErrorDialog(self.dialog, markup='<b>%s</b>' % msg)
                d.run()
                d.destroy()
Exemple #15
0
 def plugin_view_menu_items(self):
     item = gtk.ImageMenuItem(_('Open Last.FM User Page'))
     item.set_image(gtk.image_new_from_stock(gtk.STOCK_HOME,
                                             gtk.ICON_SIZE_MENU))
     def user_page_activate(widget):
         mesk.utils.load_web_page('http://www.last.fm/user/%s' %
                                  mesk.uri.escape_path(self.user))
     item.connect('activate', user_page_activate)
     return [item]
Exemple #16
0
    def _load_plugin(self, plugin_file):
        plugin = None

        (module, ext) = os.path.splitext(plugin_file)
        if not os.path.isfile(plugin_file) or ext != '.py':
            # Non plugin file
            return None

        module = os.path.basename(module)
        try:
            mesk.log.debug(_('Loading plugin %s') % module)
            __import__(module)
            module = sys.modules[module]
            plugin_info = self._get_plugin_info(module)
            if plugin_info:
                self.__all_plugins[plugin_info['NAME']] = plugin_info
        except Exception, ex:
            mesk.log.error(
                _('Plugin \'%s\' failed to load: %s') % (module, str(ex)))
            return None
Exemple #17
0
    def get_config_widget(self, parent):
        if self.config_glade:
            del self.config_glade

        WIDGET_NAME = 'gajimstatus_config_vbox'
        self.config_glade = gtk.glade.XML('./plugins/plugins_gui.glade',
                                          WIDGET_NAME, 'mesk')
        self.config_glade.signal_autoconnect(self)
        self.config_widget = self.config_glade.get_widget(WIDGET_NAME)

        vbox = self.config_glade.get_widget('accounts_vbox')
        # Remove current widgets
        children = vbox.get_children()
        for c in children:
            vbox.remove(c)

        # Check if gajim is running and list accounts if so
        output = self.gajim_remote('list_accounts')
        self.log.debug('Gajim list_accounts output: %s' % str(output))
        err_msg = None
        if output is None:
            err_msg = _(
                'Gajim must be running in order to determine accounts.')
        elif output is False:
            err_msg = _('Unable to determine Gajim accounts.')

        if err_msg:
            vbox.add(gtk.Label(err_msg))
        else:
            # Add a checkbox for each account
            config_accts = mesk.config.getlist(self.name, 'accounts')
            for acct in output:
                check_button = gtk.CheckButton(label=acct, use_underline=False)
                check_button.set_active(acct in config_accts)
                vbox.add(check_button)
                check_button.show()

        format = mesk.config.get(self.name, 'status_format', DEFAULT_FORMAT)
        self.config_glade.get_widget('format_entry').set_text(format)

        return self.config_widget
Exemple #18
0
    def __init__(self, device, track_num, meta_data=None):
        # gnomevfs does not like cdda:// uris, workaround
        AudioSource.__init__(self, "file://dummy", meta_data)
        self.uri = mesk.uri.CddaURI(track_num)

        self.block_device = device
        if not meta_data:
            self.meta_data = AudioMetaData()

        # Use URI if we don't have a title
        if not self.meta_data.title:
            self.meta_data.title = unicode(_('Track #%d') % track_num)
Exemple #19
0
    def __init__(self):
        self.xml = mesk.gtk_utils.get_glade('about_dialog',
                                            'about_dialog.glade')
        self.xml.signal_autoconnect(self)
        self.dialog = self.xml.get_widget('about_dialog')
        self.dialog.set_name("Mesk")
        self.dialog.set_version(
            "%s (%s)" % (mesk.info.APP_VERSION, mesk.info.APP_CODENAME))
        self.dialog.set_license(mesk.info.GPLV2_LICENCE)

        img = gtk.Image()
        img.set_from_file('data/images/mesk_felon.png')
        self.dialog.set_logo(img.get_pixbuf())

        # XXX: How to you get markup in the comments??

        exts = mesk.audio.supported_extensions.keys()
        exts.sort()
        formats = _('Supported audio formats: %s\n') % ','.join(exts)

        def version_to_str(version_tuple):
            s = ''
            for num in version_tuple:
                s += str(num) + '.'
            return s[0:-1]  # remove trailing dot

        extra = _('Gtk+ version: %s\n') % version_to_str(gtk.gtk_version)
        extra += _('PyGtk version: %s\n') % version_to_str(gtk.pygtk_version)
        extra += _('Gstreamer version: %s\n') % \
                 version_to_str(gst.pygst_version)
        extra += _('Installed in %s\n') % mesk.info.INSTALL_PREFIX

        self.dialog.set_comments(
            (_('A Gtk+ audio player\n\n') + formats + extra))
Exemple #20
0
    def _on_plugin_toggled(self, cell, path, model):
        new_state = not model[path][0]
        path = int(path)
        plugin_name = self._plugins[path]['NAME']

        plugin_mgr = mesk.plugin.get_manager()
        state_str = ''
        try:
            if new_state:
                state_str = _('Plugin activation error')
                plugin_mgr.activate_plugin(plugin_name)
            else:
                state_str = _('Plugin deactivation error')
                plugin_mgr.deactivate_plugin(plugin_name)
            # Update model
            model[path][0] = new_state
        except Exception, ex:
            d = ErrorDialog(self.window)
            d.set_markup('<b>%s</b>' % state_str)
            d.format_secondary_text(str(ex))
            d.run()
            d.destroy()
Exemple #21
0
 def submit(self, audio_src = None, interval = 0.1):
     # The audio_src is optional to allow flushing the queue
     if audio_src:
         # Half the song or 240s, whichever is shorter
         src_len = audio_src.meta_data.time_secs
         if src_len < 30:
             self.log.info(_('Source length %s < 30s, skipping') % \
                           str(src_len))
             return
         interval = min(src_len / 2, 240)
     # Schedule submit task
     self.submit_task = threading.Timer(interval, self._submit,
                                        args=[audio_src])
     self.submit_task.start()
Exemple #22
0
    def config_ok(self):
        username = self.config_glade.get_widget('username_entry').get_text()
        pw = self.config_glade.get_widget('password_entry').get_text()
        pw_verify = \
            self.config_glade.get_widget('password_verify_entry').get_text()
        active = self.config_glade.get_widget('submit_checkbutton').get_active()

        # Validate input
        if not username or not pw or not pw_verify:
            d = gtk.MessageDialog(self.config_parent, gtk.DIALOG_MODAL,
                                  type=gtk.MESSAGE_ERROR,
                                  buttons=gtk.BUTTONS_OK,
                                  message_format=_('Username and password '
                                                   'required'))
            d.run()
            d.destroy()
            return
        elif pw != pw_verify:
            d = gtk.MessageDialog(self.config_parent, gtk.DIALOG_MODAL,
                                  type=gtk.MESSAGE_ERROR,
                                  buttons=gtk.BUTTONS_OK,
                                  message_format=_('Passwords do not match'))
            d.run()
            d.destroy()
            return

        # Update state
        mesk.config.set(CONFIG_SECTION, 'username', username)
        mesk.config.set(CONFIG_SECTION, 'password', pw)
        mesk.config.set(CONFIG_SECTION, 'submit_tracks', active)
        self.user = username
        self.passwd = pw
        self.md5_passwd = self.get_md5(self.passwd)
        self.submit_tracks = active

        # Rehandshake with new creds
        self.handshake()
Exemple #23
0
    def _submit(self, audio_src):
        audio_src_data = self.get_submit_data(audio_src)

        self.queue_lock.acquire()

        if not self.may_submit.isSet():
            if audio_src_data:
                self.log.debug('Handshake required, queueing: %s' % \
                               str(audio_src_data))
                self._update_status(state=_('Authentication required'))
                self.queue.append(audio_src_data)
                self._update_status(queue_count=len(self.queue))
            self.queue_lock.release()
            return

        secret = self.get_md5(self.md5_passwd + self.md5_challenge)

        if audio_src_data:
            self.queue.append(audio_src_data)

        # Process queue
        done = (len(self.queue) == 0)
        while not done:
            submit_dict = {'u': self.user.encode('utf-8'),
                           's': secret}
            submit_count = min(MAX_SUBMIT, len(self.queue))
            for i in range(submit_count):
                queue_dict = self.queue[i]
                for name, value in queue_dict.items():
                    submit_dict[name % {'index': i}] = value
            submit_str = urllib.urlencode(submit_dict)

            if not self._submit_post(submit_str):
                done = True
                continue
            else:
                self.status_submitted += submit_count
                self.status_last_submit = time.asctime()

            self.queue = self.queue[submit_count:]
            if len(self.queue) == 0:
                done = True

        self.queue_lock.release()
        self.status_queued = len(self.queue)
        self._update_status()
Exemple #24
0
    def _remote_control(self, interface, cmd, arg):
        cmd_method = cmd[2:].replace('-', '_')
        cmd_method = getattr(interface, cmd_method)
        mesk.log.debug("executing remote command: %s, val: %s" % (cmd,
                                                                  str(arg)))

        if cmd in ['--stop', '--play', '--pause', '--play-pause', '--next',
                   '--prev', '--toggle-mute', '--toggle-visible',
                   '--raise-window']:
            # These commands do not take arguments or require special
            # processing, they can be invoked directly
            cmd_method()
        elif cmd in ['--get-state', '--get-current-uri', '--get-current-title',
                     '--get-current-artist', '--get-current-album',
                     '--get-current-year', '--get-current-length',
                     '--list-playlists', '--get-active-playlist']:
            # These commands output something and require a trailing '\n'
            data = cmd_method()
            if isinstance(data, list):
                for item in data:
                    self._output(item + '\n')
            elif data:
                self._output(data + '\n')

        elif cmd in ['--vol-up', '--vol-down']:
            # These commands take an argument and output nothing
            cmd_method(arg)
        elif cmd in ['--set-active-playlist']:
            # Non-generic command
            if interface.set_active_playlist(arg):
                self._output(_('Playlist \'%s\' is now active\n') % arg)
            else:
                raise RemoteControlException('Playlist %s does not exist.' %
                                             arg)
        elif cmd == '--enqueue':
            if not interface.enqueue(arg):
                raise RemoteControlException('No playlist found to enqueue URI')
        else:
            raise RemoteControlException("Unknown command \'%s\'" % cmd)

        return 0
Exemple #25
0
    def _on_sync_button_clicked(self, button):
        self._dir_count = 0
        self.sync_button.set_sensitive(False)
        self.sync_stop_button.set_sensitive(True)
        self.sync_progressbar.set_fraction(0.0)
        self.sync_progressbar.set_text(_('Scanning directories ...'))
        mesk.gtk_utils.update_pending_events()

        # Note, these callbacks happen on the sync thread, not gtk
        def progress_cb(dir_path, dir_total):
            '''Called back for each file sync'd'''
            self._dir_count += 1
            gobject.idle_add(
                lambda: self._update_sync_progress(dir_path, dir_total))

        def status_cb(status):
            '''Called back when the sync is canceled or complete'''
            completed = (status == mesk.utils.DirScannerThread.STATUS_COMPLETE)
            gobject.idle_add(lambda: self._complete_sync_progress(completed))

        mesk.database.db.sync(status_cb, progress_cb)
Exemple #26
0
    def get_submit_data(self, audio_src):
        '''Returns a dictionary of name value pairs.  The %(index)d is
        meant to be replaced with % formatting'''
        if not audio_src:
            return None

        artist = audio_src.meta_data.artist
        title = audio_src.meta_data.title
        if not artist or not title:
            self.log.warning(_('Source %s is missing artist and/or title: ') % \
                             audio_src.uri.path)
            return None
        album = audio_src.meta_data.album

        post = {}
        post['a[%(index)d]'] = artist.encode('utf-8')
        post['t[%(index)d]'] = title.encode('utf-8')
        post['b[%(index)d]'] = album.encode('utf-8')
        post['m[%(index)d]'] = ''
        post['l[%(index)d]'] = str(audio_src.meta_data.time_secs)
        post['i[%(index)d]'] = self.time_stamp()

        return post
Exemple #27
0
    def __init__(self):
        # All available plugins {name: plugin_dict}
        self.__all_plugins = {}
        # Active plugins: {name: plugin_instance}
        self.__active_plugins = {}

        sys.path.append(mesk.DEFAULT_PLUGINS_DIR)
        sys.path.append(self.SYS_PLUGINS_DIR)

        def load_plugins(root):
            mesk.log.debug('Loading plugins in %s...' % root)
            plugins = []
            for file in os.listdir(root):
                file = root + os.sep + file
                plugin = self._load_plugin(file)
                if plugin:
                    plugins.append(plugin)
            return plugins

        # The order of these calls dictates that user plugins override
        # system plugins
        load_plugins(self.SYS_PLUGINS_DIR)
        load_plugins(mesk.config.get(mesk.CONFIG_MAIN, 'plugins_dir'))

        # Activate plugins specified in config
        active_plugins_config = mesk.config.getlist(mesk.CONFIG_MAIN,
                                                    'plugins')
        for plugin_info in self.__all_plugins.values():
            plugin_name = plugin_info['NAME']
            if plugin_name in active_plugins_config:
                self.activate_plugin(plugin_name)
                i = active_plugins_config.index(plugin_name)
                del active_plugins_config[i]
        # Warn about plugins in the config that were not activated
        for name in active_plugins_config:
            mesk.log.warning(_('Plugin not found: %s') % name)
Exemple #28
0
 def on_plugin_audio_pause(self, audio_src):
     self.change_status(audio_src, '[%s]' % _('paused'), interval=2.5)
Exemple #29
0
    "b+^$/$($n#o#*#Q@u@v@_$:$<$[$}$=#*$|$1$2$3$4$4$3$5$6$7$8$9$H# $l@[@}@|@m@I@J@^#i#I#~$0$a$s+  ",
    "b+b$c$d$e$f$g$h$i$j$k$l$m$n$o$p$q$r$s$t$u$v$v$w$x$y$z$A$B$H# $l@[@}@|@m@C$D$E$F$G$H$I$J$r+  ",
    "b+K$L$M$M$N$N$O$P$Q$R$S$T$U$R@=#V$W$X$Y$Z$`$ %.%+%@%#%$%%%H# $l@[@}@|@m@&%*%=%-%-%=%;%>%b+b+",
    "b+b+s+q@q@q@q@t+,%'%)%!%~%{%R@=#]%^%/%(%_%:%<%[%}%|%1%2%3%H# $l@[@}@|@m@4%5%M+s+s+s+s+:#b+b+",
    "            b+6%7%8%9%0%a%b%R@=#]%c%d%e%f%g%h%i%j%k%l%m%3%H# $l@[@}@|@m@n%o%s+              ",
    "            b+*@p%q%r%s%t%u%R@=#]%c%d%e%f%v%w%i%j%x%l%m%3%H# $l@[@}@|@m@y%z%A%b+            ",
    "            s+B%Q$C%D%E%F%G%R@=#]%c%d%e%f%v%w%i%j%x%l%m%3%H# $l@[@}@|@m@H%I%4@b+            ",
    "          b+M+J%K%L%M%E%F%G%R@=#]%c%d%e%f%v%w%i%j%x%l%m%3%H# $l@[@}@|@m@I@N%O%b+            ",
    "          b+P%Q%R%S%T%E%U%V%R@=#]%c%d%e%f%v%w%i%j%x%l%m%3%H# $l@[@}@|@m@I@W%X%b+            ",
    "          b+Y%Z%`% &.&+&@&#&R@=#]%c%d%e%f%v%w%i%j%x%l%m%3%H# $l@[@}@|@m@I@$&%&b+            ",
    "          b+&&*&=&-&.&+&;&{%R@=#]%c%d%e%f%v%w%i%j%x%l%m%3%H# $l@[@}@|@m@I@>&o%s+            ",
    "          b+,&'&)&!&.&+&T$U$R@=#]%c%d%e%f%v%w%i%j%x%l%m%3%H# $l@[@}@|@m@I@~&{&M+b+          ",
    "          s+]&^&/&(&.&+&m$n$_&=#]%c%d%e%f%v%w%i%j%x%l%m%3%H# $l@[@}@|@m@I@J@:&##b+          ",
    "          s+<&[&}&|&1&2&3&q#4&=#]%c%d%e%f%v%w%i%j%x%l%m%3%H# $l@[@}@|@m@I@J@5&&@b+          ",
    "          s+6&7&8&9&0&a&b&c&d&e&f&g&h&i&j&j&k&l&m&n&o&p&q&r&s&s&t&u&v&w&x&y&:&&@b+          ",
    "          b+Y%L#z&z&z&A&A&A&A&A&B&B&C&D&E&E&F&F&G&G&H&H&H&H&H&I&I&J&J&J&J&K&L&s+b+          ",
    "          b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+b+          "
]

## Required for plugins ##
PLUGIN_INFO = PluginInfo(name='gajimstatus',
                         desc=_('Uses Gajim (a Jabber/XMPP instant '
                                'message client) to update your status '
                                'message with currently playing song info.'),
                         author='Travis Shirk <*****@*****.**>',
                         url='http://mesk.nicfit.net/',
                         copyright='Copyright © 2006-2007 Travis Shirk',
                         clazz=GajimStatusPlugin,
                         xpm=XPM,
                         display_name='Gajim Status')
Exemple #30
0
class AudioscrobblerPlugin(Plugin, AudioControlListener, ViewMenuProvider):

    def __init__(self):
        Plugin.__init__(self, PLUGIN_INFO)

        self.user = ''
        self.passwd = ''
        self.submit_tracks = True
        self.md5_passwd = None
        self.last_handshake = None
        self.md5_challenge = None
        self.submit_url = None

        # Status label values
        self.status_state = 'Auth Required'
        self.status_submitted = 0
        self.status_queued = 0
        self.status_last_submit = 'None'
        self.status_labels = {}

        self.queue_lock = threading.Lock()
        self.queue = []
        profile = ''
        if mesk.config.profile:
            profile = '_%s' % mesk.config.profile
        self.queue_filename = mesk.MESK_DIR + os.sep + 'tmp' + os.sep + \
                              ('%s%s.queue' % (NAME, profile))

        self.may_submit = threading.Event()
        self.handshake_task = None
        self.submit_task = None

        if mesk.config.has_section(CONFIG_SECTION):
            self.user = mesk.config.get(CONFIG_SECTION, 'username')
            self.passwd = mesk.config.get(CONFIG_SECTION, 'password')
            self.queue_filename = mesk.config.get(CONFIG_SECTION, 'queue_file')
            self.submit_tracks = mesk.config.getboolean(CONFIG_SECTION,
                                                        'submit_tracks', True)
            self.md5_passwd = self.get_md5(self.passwd)
            self.handshake()
        else:
            mesk.config.add_section(CONFIG_SECTION)
            mesk.config.set(CONFIG_SECTION, 'username', self.user)
            mesk.config.set(CONFIG_SECTION, 'password', self.passwd)
            mesk.config.set(CONFIG_SECTION, 'submit_tracks', self.submit_tracks)
            mesk.config.set(CONFIG_SECTION, 'queue_file', self.queue_filename)

        q_dir = os.path.dirname(self.queue_filename)
        if os.path.exists(self.queue_filename):
            self.queue = pickle.load(open(self.queue_filename, 'r'))
            self.status_queued = len(self.queue)
            self.log.verbose('Loaded %d queued tracks' % self.status_queued)
        elif not os.path.exists(q_dir):
            # Create tmp dir for queue
            self.log.verbose('Creating queue file directory')
            os.makedirs(q_dir)

        self.bad_auth_dialog = None

    def shutdown(self):
        self.log.debug('Shutting down...')
        self._cancel_handshake()
        self._cancel_submit()

        # Save pending queue
        self.log.verbose('Saving %d queued tracks' % len(self.queue))
        pickle.dump(self.queue, open(self.queue_filename, 'w'))

    def is_configurable(self):
        return True

    def get_config_widget(self, parent):
        self.config_parent = parent
        from mesk.gtk_utils import default_linkbutton_callback
        self.config_glade = gtk.glade.XML('./plugins/plugins_gui.glade',
                                          'audioscrobbler_config_vbox', 'mesk')
        self.config_glade.signal_autoconnect(self)
        self.config_widget = \
            self.config_glade.get_widget('audioscrobbler_config_vbox')
        # Add linkbuttons since glade2 does not support them yet
        join_url = 'http://www.last.fm/signup.php'
        group_url = 'http://www.last.fm/group/Mesk%2BUsers/join/'
        join_button = gtk.LinkButton(join_url, 'Join Last.FM')
        join_button.connect('clicked', default_linkbutton_callback)
        group_button = gtk.LinkButton(group_url,
                                      'Join the Mesk Last.FM group')
        group_button.connect('clicked', default_linkbutton_callback)
        hbox = self.config_glade.get_widget('linkbutton_hbox')
        hbox.pack_start(join_button)
        hbox.pack_start(group_button)
        hbox.show_all()

        self.status_labels = {
            'state': self.config_glade.get_widget('state_label'),
            'submitted': self.config_glade.get_widget('submitted_label'),
            'queued': self.config_glade.get_widget('queued_label'),
            'last': self.config_glade.get_widget('last_submit_label'),
            }

        # Populate current config values
        self.config_glade.get_widget('username_entry').set_text(self.user)
        self.config_glade.get_widget('password_entry').set_text(self.passwd)
        self.config_glade.get_widget('password_verify_entry')\
                         .set_text(self.passwd)

        self._update_status()

        enabled_checkbutton = self.config_glade.get_widget('submit_checkbutton')
        enabled_checkbutton.set_active(self.submit_tracks)
        enabled_checkbutton.emit('toggled')

        return self.config_widget

    def _update_status(self, state=None, submit_count=None, queue_count=None,
                       last_submit=None):
        if state is not None:
            self.status_state = state
        if submit_count is not None:
            self.status_submitted = submit_count
        if queue_count is not None:
            self.status_queued = queue_count
        if last_submit is not None:
            self.status_last_submit = last_submit

        def idle_update():
            # This must always execute on the gui thread
            if not self.status_labels:
                return
            self.status_labels['state'].set_text(self.status_state)
            self.status_labels['submitted'].set_text(str(self.status_submitted))
            self.status_labels['queued'].set_text(str(self.status_queued))
            self.status_labels['last'].set_text(self.status_last_submit)
        gobject.idle_add(idle_update)

    def _on_enable_checkbutton_toggled(self, checkbutton):
        # Set all table children's sensitivity based on enabled state
        table = self.config_glade.get_widget('profile_info_table')
        for child in table.get_children():
            child.set_sensitive(checkbutton.get_active())

    def config_ok(self):
        username = self.config_glade.get_widget('username_entry').get_text()
        pw = self.config_glade.get_widget('password_entry').get_text()
        pw_verify = \
            self.config_glade.get_widget('password_verify_entry').get_text()
        active = self.config_glade.get_widget('submit_checkbutton').get_active()

        # Validate input
        if not username or not pw or not pw_verify:
            d = gtk.MessageDialog(self.config_parent, gtk.DIALOG_MODAL,
                                  type=gtk.MESSAGE_ERROR,
                                  buttons=gtk.BUTTONS_OK,
                                  message_format=_('Username and password '
                                                   'required'))
            d.run()
            d.destroy()
            return
        elif pw != pw_verify:
            d = gtk.MessageDialog(self.config_parent, gtk.DIALOG_MODAL,
                                  type=gtk.MESSAGE_ERROR,
                                  buttons=gtk.BUTTONS_OK,
                                  message_format=_('Passwords do not match'))
            d.run()
            d.destroy()
            return

        # Update state
        mesk.config.set(CONFIG_SECTION, 'username', username)
        mesk.config.set(CONFIG_SECTION, 'password', pw)
        mesk.config.set(CONFIG_SECTION, 'submit_tracks', active)
        self.user = username
        self.passwd = pw
        self.md5_passwd = self.get_md5(self.passwd)
        self.submit_tracks = active

        # Rehandshake with new creds
        self.handshake()

    def _on_dialog_close(self, dialog, response):
        self.bad_auth_dialog.destroy()
        self.bad_auth_dialog = None

    def get_md5(self, s):
        hash = md5.new()
        hash.update(s)
        return hash.hexdigest()

    def handshake(self, delay=0.1):
        # Cancel any pending
        self._cancel_handshake()

        self.may_submit.clear()
        self.md5_challenge, self.submit_url = None, None
        # Async handshake
        self.handshake_task = threading.Timer(delay, self._handshake)
        self.handshake_task.start()

    def _handshake(self):
        # Update user/passwd from config if necessary
        if not self.user or not self.passwd:
            self.user = mesk.config.get(CONFIG_SECTION, 'username', '')
            self.passwd = mesk.config.get(CONFIG_SECTION, 'password', '')
            self.md5_passwd = self.get_md5(self.passwd)
            if not self.user or not self.passwd:
                self.log.critical('Add a username and password to the '
                                  'audioscrobbler plugin preferences.')
                self._update_status(state=_('No username and/or password'))
                return

        # Open handshake URL
        hs_url = HANDSHAKE_URL % (PROTOCOL_VERSION, APP, VERSION, self.user)
        url_data = None
        try:
            self.log.debug('Handshake: %s' % hs_url)
            url_data = urllib2.urlopen(hs_url)
        except Exception, ex:
            retry = 10 # minutes
            self.log.warning('Handshake error, retry in %d minutes: %s' %
                             (retry, str(ex)))
            self._update_status(state=_('Cannot connect to Last.FM'))
            # Fatal failure, retry in 10 minutes
            self.handshake(retry * 60)
            return

        # Parse response
        try:
            response = url_data.read()
            url_data.close()
            response = response.split('\n')
            self.log.debug('Handshake response: %s' % str(response))
            response_head = response[0].split(' ', 1)
            response_code = response_head[0]
            response_extra = ''
            if len(response_head) == 2:
                response_extra = response_head[1]

            if response_code not in HANDSHAKE_CODES:
                self.log.warning('Malformed response: %s' % str(response))
                self._update_status(state=_('Last.FM error'))
                return

            # XXX: Workaround bug in server.
            #      See http://www.audioscrobbler.net/forum/21716/_/93936
            if response_code == 'BADUSER':
                while '' in response:
                    response.remove('')

            if response_code == 'FAILED' or response_code == 'BADUSER':
                interval = int(response[1].split()[1])
                self.log.warning(_('Handshake failure \'%s\', retrying in %d '
                                   'seconds: %s') % (response_code,
                                                     interval * 60,
                                                     response_extra))

                if response_code == 'BADUSER':
                    self._update_status(state=_('Invalid username'))
                else:
                    self._update_status(state=_('Authentication failed'))

                # Retry handshake in interval seconds
                self.handshake(interval * 60)
                return

            if response_code == 'UPDATE':
                self.log.info(_('Plugin update available here, please see %s') \
                              % response_extra)

            self.md5_challenge = response[1]
            self.submit_url = response[2]
            interval = int(response[3].split()[1])
        except IndexError, ex:
            self.log.warning('Invalid response: %s' % str(response))
            self._update_status(state=_('Last.FM error'))
            return