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)
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
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())
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
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
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')
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
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
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()
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()
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]
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()
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))
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()
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]
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
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
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)
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))
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()
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()
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 _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()
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
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)
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
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)
def on_plugin_audio_pause(self, audio_src): self.change_status(audio_src, '[%s]' % _('paused'), interval=2.5)
"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')
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