#!/usr/bin/env python # Copyright (C) 2006 Adam Olsen # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 1, or (at your option) # any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. import pygtk, manager pygtk.require('2.0') import gtk, pango, gtk.glade, gobject, sys, os, plugins, urllib, re from xl import common, xlmisc from gettext import gettext as _ def show_error(parent, message): """ Shows an error dialog """ dialog = gtk.MessageDialog(parent, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, message) dialog.run() dialog.destroy() class PluginManager(object): """ Gui to manage plugins """ def __init__(self, app, parent, manager, update, avail_url=''): """ Initializes the manager params: parent window, plugin manager """ self.app = app self.parent = parent self.update = update self.manager = manager self.xml = gtk.glade.XML('plugins/plugins.glade', 'PluginManagerDialog','exaile') self.dialog = self.xml.get_widget('PluginManagerDialog') self.dialog.set_transient_for(parent) self.plugin_nb = self.xml.get_widget('plugin_notebook') self.list = self.xml.get_widget('plugin_tree') self.version_label = self.xml.get_widget('version_label') self.author_label = self.xml.get_widget('author_label') self.name_label = self.xml.get_widget('name_label') self.description = self.xml.get_widget('description_view') self.model = gtk.ListStore(gtk.gdk.Pixbuf, str, bool, object) self.xml.get_widget('close_button').connect('clicked', lambda *e: self.dialog.destroy()) self.configure_button = self.xml.get_widget('configure_button') self.configure_button.connect('clicked', self.configure_plugin) self.plugin_install_button = self.xml.get_widget('plugin_install_button') self.plugin_install_button.connect('clicked', self.install_plugin) self.xml.get_widget('plugin_uninstall_button').connect('clicked', self.uninstall_plugin) pb = gtk.CellRendererPixbuf() text = gtk.CellRendererText() col = gtk.TreeViewColumn(_("Plugin")) col.pack_start(pb, False) col.pack_start(text, False) col.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED) col.set_fixed_width(1) col.set_expand(True) col.set_attributes(pb, pixbuf=0) col.set_attributes(text, text=1) self.list.append_column(col) text = gtk.CellRendererToggle() text.set_property('activatable', True) text.connect('toggled', self.toggle_cb, self.model) col = gtk.TreeViewColumn(_("Enabled"), text) col.add_attribute(text, 'active', 2) col.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE) self.list.append_column(col) self.load_plugin_list() selection = self.list.get_selection() selection.connect('changed', self.row_selected) self.list.set_model(self.model) self.dialog.show_all() selection.select_path(0) self.fetched = False if avail_url: self.setup_avail_tab() self.avail_url = avail_url self.plugin_nb.connect('switch-page', self.check_fetch_avail) def load_plugin_list(self): self.model.clear() for plugin in self.manager.plugins: icon = plugin.PLUGIN_ICON if not icon: icon = self.dialog.render_icon('gtk-execute', gtk.ICON_SIZE_MENU) self.model.append([icon, plugin.PLUGIN_NAME, plugin.PLUGIN_ENABLED, plugin]) def install_plugin(self, *e): """ Installs the selected plugin """ self.plugin_install_button.set_sensitive(False) self.plugin_nb.set_sensitive(False) self.download_plugins() def done_installing(self, files): # now we remove all the installed plugins from the available list while True: iter = self.avail_model.get_iter_first() while True: if not iter: break file = self.avail_model.get_value(iter, 4) if file in files: self.avail_model.remove(iter) break iter = self.avail_model.iter_next(iter) if not iter: break self.plugin_install_button.set_sensitive(True) self.load_plugin_list() self.plugin_nb.set_sensitive(True) self.avail_version_label.set_text('') self.avail_author_label.set_text('') self.avail_name_label.set_markup('' + _('No Plugin Selected') + '') self.avail_description.get_buffer().set_text('') @common.threaded def download_plugins(self): """ Downloads the selected plugin """ download_dir = os.path.join(self.app.get_settings_dir(), 'plugins') files = [] iter = self.avail_model.get_iter_first() while True: if not iter: break checked = self.avail_model.get_value(iter, 5) if checked: file = self.avail_model.get_value(iter, 4) try: # if the directory does not exist, create it if not os.path.isdir(download_dir): os.mkdir(download_dir, 0777) download_url = "http://www.exaile.org/plugins/plugins.py?version=%s&plugin=%s" \ % (self.app.get_plugin_location(), file) xlmisc.log('Downloading %s from %s' % (file, download_url)) plugin = urllib.urlopen(download_url).read() h = open(os.path.join(download_dir, file), 'w') h.write(plugin) h.close() try: _name = re.sub(r'\.pyc?$', '', file) if _name in sys.modules: del sys.modules[_name] except Exception, e: xlmisc.log_exception() enabled_plugins = [] for k, v in self.app.settings.get_plugins().iteritems(): if v: enabled_plugins.append("%s.py" % k) gobject.idle_add(self.manager.initialize_plugin, download_dir, file, enabled_plugins, False) files.append(file) except Exception, e: model.set_value(iter, 5, False) gobject.idle_add(common.error, self.parent, _("%(plugin)s could " "not be installed: %(exception)s") % { 'plugin': file, 'exception': e }) xlmisc.log_exception() iter = self.avail_model.iter_next(iter) gobject.idle_add(self.done_installing, files) def check_fetch_avail(self, *e): """ Checks to see if the available plugin list needs to be fetched """ if self.plugin_nb.get_current_page() != 0 or self.fetched: return self.fetched = True self.avail_version_label.set_text('') self.avail_author_label.set_text('') self.avail_name_label.set_markup(_('No Plugin Selected')) self.avail_description.get_buffer().set_text(_("Fetching available" " plugin list...")) self.avail_model.clear() self.fetch_available_plugins(self.avail_url) xlmisc.log('Fetching available plugin list') @common.threaded def fetch_available_plugins(self, url): """ Fetches a plugin list from the specified url """ h = urllib.urlopen(url) lines = h.readlines() h.close() gobject.idle_add(self.done_fetching, lines) def done_fetching(self, lines): check = False for line in lines: line = line.strip() (file, name, version, author, description) = line.split('\t') description = description.replace("\n", " ").replace(r'\n', '\n') icon = self.dialog.render_icon('gtk-execute', gtk.ICON_SIZE_MENU) found = False for plugin in self.manager.plugins: if plugin.PLUGIN_NAME == name: #this is a bit odd, to allow non-decimal versioning. installed_ver = map(int, plugin.PLUGIN_VERSION.split('.')) available_ver = map(int, version.split('.')) if installed_ver >= available_ver: found = True if not found: self.avail_model.append([name, version, author, description, file, False]) check = True if not check: common.info(self.parent, _("No plugins or updates could be found " "for your version.")) self.avail_description.get_buffer().set_text(_("No plugin selected")) selection = self.avail_list.get_selection() selection.select_path(0) self.avail_row_selected(selection) def setup_avail_tab(self): """ Sets up the "plugins available" tab """ self.avail_model = gtk.ListStore(str, str, str, str, str, bool) self.avail_list = self.xml.get_widget('avail_plugin_tree') self.avail_version_label = self.xml.get_widget('avail_version_label') self.avail_author_label = self.xml.get_widget('avail_author_label') self.avail_name_label = self.xml.get_widget('avail_name_label') self.avail_description = self.xml.get_widget('avail_description_view') text = gtk.CellRendererText() col = gtk.TreeViewColumn(_('Plugin')) col.pack_start(text, False) col.set_expand(True) col.set_fixed_width(120) col.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED) col.set_attributes(text, text=0) self.avail_list.append_column(col) text = gtk.CellRendererText() col = gtk.TreeViewColumn(_('Ver')) col.pack_start(text, False) col.set_expand(False) col.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE) col.set_attributes(text, text=1) self.avail_list.append_column(col) text = gtk.CellRendererToggle() text.set_property('activatable', True) text.connect('toggled', self.avail_toggle_cb, self.avail_model) col = gtk.TreeViewColumn(_("Inst"), text) col.add_attribute(text, 'active', 5) col.set_expand(False) col.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE) self.avail_list.append_column(col) self.avail_list.set_model(self.avail_model) self.avail_description.get_buffer().set_text(_("Fetching available" " plugin list...")) selection = self.avail_list.get_selection() selection.connect('changed', self.avail_row_selected) def avail_row_selected(self, selection): """ Called when a user selects a row in the avialable tab """ model, iter = selection.get_selected() if not iter: return name = model.get_value(iter, 0) version = model.get_value(iter, 1) author = model.get_value(iter, 2) description = model.get_value(iter, 3) self.avail_name_label.set_markup('%s' % common.escape_xml(name)) self.avail_version_label.set_label(version) self.avail_author_label.set_label(author) self.avail_description.get_buffer().set_text(description) def avail_toggle_cb(self, cell, path, model): model[path][5] = not model[path][5] def toggle_cb(self, cell, path, model): """ Called when the checkbox is toggled """ plugin = model[path][3] model[path][2] = not model[path][2] plugin.PLUGIN_ENABLED = model[path][2] if plugin.PLUGIN_ENABLED: try: if not plugin.initialize(): PLUGIN_ENABLED = False print "Error inizializing plugin" model[path][2] = False except plugins.PluginInitException, e: show_error(self.parent, str(e)) model[path][2] = False else: plugin.destroy() plugin.PLUGIN_ENABLED = False xlmisc.log("Plugin %s set to enabled: %s" % (plugin.PLUGIN_NAME, plugin.PLUGIN_ENABLED)) if self.update: self.update(plugin) def uninstall_plugin(self, *e): """ Disables and uninstalls the selected plugin """ result = common.yes_no_dialog(self.parent, _("Are you sure " "you want to uninstall the selected plugin?")) if result == gtk.RESPONSE_YES: selection = self.list.get_selection() (model, iter) = selection.get_selected() if not iter: return plugin = model.get_value(iter, 3) if plugin.PLUGIN_ENABLED: plugin.destroy() model.remove(iter) self.manager.plugins.remove(plugin) self.manager.loaded.remove(plugin.PLUGIN_NAME) try: del sys.modules[re.sub(r'\.pyc?$', '', plugin.FILE_NAME)] os.remove(os.path.join(self.app.get_settings_dir(), 'plugins', plugin.FILE_NAME)) os.remove(os.path.join(self.app.get_settings_dir(), 'plugins', plugin.FILE_NAME + 'c')) except: xlmisc.log_exception() self.fetched = False def configure_plugin(self, *e): """ Calls the plugin's configure function """ selection = self.list.get_selection() (model, iter) = selection.get_selected() if not iter: return plugin = model.get_value(iter, 3) plugin.configure() def row_selected(self, selection, user_data=None): """ Called when a row is selected """ (model, iter) = selection.get_selected() if not iter: return plugin = model.get_value(iter, 3) self.version_label.set_label(str(plugin.PLUGIN_VERSION)) self.author_label.set_label(", ".join(plugin.PLUGIN_AUTHORS)) self.description.get_buffer().set_text(plugin.PLUGIN_DESCRIPTION.replace( "\n", " ").replace(r'\n', "\n")) self.name_label.set_markup("%s" % common.escape_xml(plugin.PLUGIN_NAME)) self.configure_button.set_sensitive(hasattr(plugin, 'configure')) class PluginTest(object): def __init__(self, name, version, author, desc): self.PLUGIN_NAME = name self.PLUGIN_VERSION = version self.PLUGIN_AUTHORS = [author] self.PLUGIN_DESCRIPTION = desc self.PLUGIN_ICON = None self.PLUGIN_ENABLED = True