diff --git a/src/calibre/gui2/dialogs/plugin_updater.py b/src/calibre/gui2/dialogs/plugin_updater.py index 0b0885dc82..123a14b829 100644 --- a/src/calibre/gui2/dialogs/plugin_updater.py +++ b/src/calibre/gui2/dialogs/plugin_updater.py @@ -23,10 +23,8 @@ from calibre.gui2 import error_dialog, question_dialog, info_dialog, NONE, open_ from calibre.gui2.preferences.plugins import ConfigWidget from calibre.utils.date import UNDEFINED_DATE, format_date - -MR_URL = 'http://www.mobileread.com/forums/' -MR_INDEX_URL = MR_URL + 'showpost.php?p=1362767&postcount=1' - +SERVER = 'http://plugins.calibre-ebook.com/' +INDEX_URL = '%splugins.json.bz2' % SERVER FILTER_ALL = 0 FILTER_INSTALLED = 1 FILTER_UPDATE_AVAILABLE = 2 @@ -55,33 +53,30 @@ def filter_not_installed_plugins(display_plugin): return not display_plugin.is_installed() def read_available_plugins(raise_error=False): + import json, bz2 display_plugins = [] br = browser() - br.set_handle_gzip(True) try: - raw = br.open_novisit(MR_INDEX_URL).read() + raw = br.open_novisit(INDEX_URL).read() if not raw: return + raw = json.loads(bz2.decompress(raw)) except: if raise_error: raise traceback.print_exc() return - raw = raw.decode('utf-8', errors='replace') - root = html.fromstring(raw) - list_nodes = root.xpath('//div[@id="post_message_1362767"]/ul/li') - # Add our deprecated plugins which are nested in a grey span - list_nodes.extend(root.xpath('//div[@id="post_message_1362767"]/span/ul/li')) - for list_node in list_nodes: + for plugin in raw.itervalues(): try: - display_plugin = DisplayPlugin(list_node) + display_plugin = DisplayPlugin(plugin) get_installed_plugin_status(display_plugin) display_plugins.append(display_plugin) except: if DEBUG: - prints('======= MobileRead Parse Error =======') + prints('======= Plugin Parse Error =======') traceback.print_exc() - prints(html.tostring(list_node)) + import pprint + pprint.pprint(plugin) display_plugins = sorted(display_plugins, key=lambda k: k.name) return display_plugins @@ -189,61 +184,21 @@ class PluginFilterComboBox(QComboBox): class DisplayPlugin(object): - def __init__(self, list_node): - # The html from the index web page looks like this: - ''' -
  • Book Sync
    -Add books to a list to be automatically sent to your device the next time it is connected.
    -Version: 1.1; Released: 02-22-2011; Calibre: 0.7.42; Author: kiwidude;
    -Platforms: Windows, OSX, Linux; History: Yes;
  • - ''' - self.name = list_node.xpath('a')[0].text_content().strip() - self.forum_link = list_node.xpath('a/@href')[0].strip() + def __init__(self, plugin): + self.name = plugin['name'] + self.forum_link = plugin['thread_url'] + self.zip_url = SERVER + plugin['file'] self.installed_version = None - - description_text = list_node.xpath('i')[0].text_content() - description_parts = description_text.partition('Version:') - self.description = description_parts[0].strip() - - details_text = description_parts[1] + description_parts[2].replace('\r\n','') - details_pairs = details_text.split(';') - details = {} - for details_pair in details_pairs: - pair = details_pair.split(':') - if len(pair) == 2: - key = pair[0].strip().lower() - value = pair[1].strip() - details[key] = value - - donation_node = list_node.xpath('i/span/a/@href') - self.donation_link = donation_node[0] if donation_node else None - - self.available_version = self._version_text_to_tuple(details.get('version', None)) - - release_date = details.get('released', '01-01-0101').split('-') - date_parts = [int(re.search(r'(\d+)', x).group(1)) for x in release_date] - self.release_date = datetime.date(date_parts[2], date_parts[0], date_parts[1]) - - self.calibre_required_version = self._version_text_to_tuple(details.get('calibre', None)) - self.author = details.get('author', '') - self.platforms = [p.strip().lower() for p in details.get('platforms', '').split(',')] - # Optional pairing just for plugins which require checking for uninstall first - self.uninstall_plugins = [] - uninstall = details.get('uninstall', None) - if uninstall: - self.uninstall_plugins = [i.strip() for i in uninstall.split(',')] - self.has_changelog = details.get('history', 'No').lower() in ['yes', 'true'] - self.is_deprecated = details.get('deprecated', 'No').lower() in ['yes', 'true'] - - def _version_text_to_tuple(self, version_text): - if version_text: - ver = version_text.split('.') - while len(ver) < 3: - ver.append('0') - ver = [int(re.search(r'(\d+)', x).group(1)) for x in ver] - return tuple(ver) - else: - return None + self.description = plugin['description'] + self.donation_link = plugin['donate'] + self.available_version = tuple(plugin['version']) + self.release_date = datetime.datetime(*tuple(map(int, re.split(r'\D', plugin['last_modified'])))[:6]).date() + self.calibre_required_version = plugin['minimum_calibre_version'] + self.author = plugin['author'] + self.platforms = plugin['supported_platforms'] + self.uninstall_plugins = plugin['uninstall'] or [] + self.has_changelog = plugin['history'] + self.is_deprecated = plugin['deprecated'] def is_disabled(self): if self.plugin is None: @@ -456,6 +411,7 @@ class PluginUpdaterDialog(SizePersistedDialog): SizePersistedDialog.__init__(self, gui, 'Plugin Updater plugin:plugin updater dialog') self.gui = gui self.forum_link = None + self.zip_url = None self.model = None self.do_restart = False self._initialize_controls() @@ -475,8 +431,8 @@ class PluginUpdaterDialog(SizePersistedDialog): self._select_and_focus_view() else: error_dialog(self.gui, _('Update Check Failed'), - _('Unable to reach the MobileRead plugins forum index page.'), - det_msg=MR_INDEX_URL, show=True) + _('Unable to reach the plugin index page.'), + det_msg=INDEX_URL, show=True) self.filter_combo.setEnabled(False) # Cause our dialog size to be restored from prefs or created on first usage self.resize_dialog() @@ -599,6 +555,7 @@ class PluginUpdaterDialog(SizePersistedDialog): display_plugin = self.model.display_plugins[actual_idx.row()] self.description.setText(display_plugin.description) self.forum_link = display_plugin.forum_link + self.zip_url = display_plugin.zip_url self.forum_action.setEnabled(bool(self.forum_link)) self.install_button.setEnabled(display_plugin.is_valid_to_install()) self.install_action.setEnabled(self.install_button.isEnabled()) @@ -611,6 +568,7 @@ class PluginUpdaterDialog(SizePersistedDialog): else: self.description.setText('') self.forum_link = None + self.zip_url = None self.forum_action.setEnabled(False) self.install_button.setEnabled(False) self.install_action.setEnabled(False) @@ -703,17 +661,7 @@ class PluginUpdaterDialog(SizePersistedDialog): for name_to_remove in uninstall_names: self._uninstall_plugin(name_to_remove) - if DEBUG: - prints('Locating zip file for %s: %s'% (display_plugin.name, display_plugin.forum_link)) - self.gui.status_bar.showMessage( - _('Locating zip file for %(name)s: %(link)s') % dict( - name=display_plugin.name, link=display_plugin.forum_link)) - plugin_zip_url = self._read_zip_attachment_url(display_plugin.forum_link) - if not plugin_zip_url: - return error_dialog(self.gui, _('Install Plugin Failed'), - _('Unable to locate a plugin zip file for %s') % display_plugin.name, - det_msg=display_plugin.forum_link, show=True) - + plugin_zip_url = display_plugin.zip_url if DEBUG: prints('Downloading plugin zip attachment: ', plugin_zip_url) self.gui.status_bar.showMessage(_('Downloading plugin zip attachment: %s') % plugin_zip_url) @@ -852,39 +800,11 @@ class PluginUpdaterDialog(SizePersistedDialog): prints(html.tostring(spoiler_node)) return None - def _read_zip_attachment_url(self, forum_link): - br = browser() - br.set_handle_gzip(True) - try: - raw = br.open_novisit(forum_link).read() - if not raw: - return None - except: - traceback.print_exc() - return None - raw = raw.decode('utf-8', errors='replace') - root = html.fromstring(raw) - attachment_nodes = root.xpath('//fieldset/table/tr/td/a') - for attachment_node in attachment_nodes: - try: - filename = attachment_node.text_content().lower() - if filename.find('.zip') != -1: - full_url = MR_URL + attachment_node.attrib['href'] - return full_url - except: - if DEBUG: - prints('======= MobileRead Parse Error =======') - traceback.print_exc() - prints(html.tostring(attachment_node)) - return None - def _download_zip(self, plugin_zip_url): from calibre.ptempfile import PersistentTemporaryFile br = browser() - br.set_handle_gzip(True) raw = br.open_novisit(plugin_zip_url).read() - pt = PersistentTemporaryFile('.zip') - pt.write(raw) - pt.close() + with PersistentTemporaryFile('.zip') as pt: + pt.write(raw) return pt.name