[Dextrose] [PATCH] Replace activity updater with microformat compatible one

Anish Mangal anishmangal2002 at gmail.com
Mon Jan 3 15:18:22 EST 2011


Currently, the patch checks for the activity size via an http request
if it doesn't find the tag olpc-activity-size, regardless of whether
the activity is meant to be updated. There was a discussion in a
related thread that suggested displaying the size in the Updater UI
altogether. I'll modify the patch as per the result of the discussion.

Note: This patch substitutes the old updater with the new one.
Currently the update url points to
http://wiki.paraguayeduca.org/index.php/Actividades_Dextrose_1

This may be set to [1]  or using a gconf key as is best seen fit.

[1] http://activities-testing.sugarlabs.org/services/micro-format.php?collection_nickname=fructose&lang=en

On Mon, Jan 3, 2011 at 5:08 PM, Anish Mangal <anish at sugarlabs.org> wrote:
> This patch replaces the Sugar activity updater with one, that supports
> activity microformats.
>
> + The updater now allows installation of new
> activities which were not previously installed.
>
> + The updater uses the optional olpc-activity-name and
> olpc-activity-size tags.
>
>   - If olpc-activity-name is not present, the activity name is derived
>     from the bundle id. For example, org.Sugarlabs.RecordActivity will
>     be listed as Record.
>
>   - If olpc-activity-size is not present, an additional http request is
>     made to ascertain the size of the bundle. If the size returned as
>     zero, the bundle is removed from the list of those which may be
>     updated.
>
> + To install new bundles, a metabundle class has been created, which
> acts as an empty structure.
>
> Consider this as a request to test drive and review this patch :-)
>
> Co-Authored by Anish Mangal <anish at sugarlabs.org>
> Co-Authored by Akash Gangil <akashg1611 at gmail.com>
>
> Signed-off-by: Anish Mangal <anish at sugarlabs.org>
> ---
>  extensions/cpsection/updater/backends/Makefile.am  |    2 +-
>  extensions/cpsection/updater/backends/aslo.py      |  164 ----------------
>  .../cpsection/updater/backends/microformat.py      |  202 ++++++++++++++++++++
>  extensions/cpsection/updater/model.py              |   85 ++++++---
>  extensions/cpsection/updater/view.py               |   18 ++-
>  5 files changed, 272 insertions(+), 199 deletions(-)
>  delete mode 100644 extensions/cpsection/updater/backends/aslo.py
>  create mode 100644 extensions/cpsection/updater/backends/microformat.py
>  mode change 100755 => 100644 extensions/cpsection/updater/model.py
>
> diff --git a/extensions/cpsection/updater/backends/Makefile.am b/extensions/cpsection/updater/backends/Makefile.am
> index e280a07..4e2d6be 100644
> --- a/extensions/cpsection/updater/backends/Makefile.am
> +++ b/extensions/cpsection/updater/backends/Makefile.am
> @@ -1,5 +1,5 @@
>  sugardir = $(pkgdatadir)/extensions/cpsection/updater/backends
>
>  sugar_PYTHON =                 \
> -       aslo.py         \
> +       microformat.py \
>        __init__.py
> diff --git a/extensions/cpsection/updater/backends/aslo.py b/extensions/cpsection/updater/backends/aslo.py
> deleted file mode 100644
> index 6504e9e..0000000
> --- a/extensions/cpsection/updater/backends/aslo.py
> +++ /dev/null
> @@ -1,164 +0,0 @@
> -#!/usr/bin/python
> -# Copyright (C) 2009, Sugar Labs
> -#
> -# 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 2 of the License, 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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
> -
> -"""Activity information microformat parser.
> -
> -Activity information is embedded in HTML/XHTML/XML pages using a
> -Resource Description Framework (RDF) http://www.w3.org/RDF/ .
> -
> -An example::
> -
> -<?xml version="1.0" encoding="UTF-8"?>
> -<RDF:RDF xmlns:RDF="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
> -        xmlns:em="http://www.mozilla.org/2004/em-rdf#">
> -<RDF:Description about="urn:mozilla:extension:bounce">
> -    <em:updates>
> -        <RDF:Seq>
> -            <RDF:li resource="urn:mozilla:extension:bounce:7"/>
> -        </RDF:Seq>
> -    </em:updates>
> -</RDF:Description>
> -
> -<RDF:Description about="urn:mozilla:extension:bounce:7">
> -    <em:version>7</em:version>
> -    <em:targetApplication>
> -        <RDF:Description>
> -            <em:id>{3ca105e0-2280-4897-99a0-c277d1b733d2}</em:id>
> -            <em:minVersion>0.82</em:minVersion>
> -            <em:maxVersion>0.84</em:maxVersion>
> -            <em:updateLink>http://foo.xo</em:updateLink>
> -            <em:updateSize>7</em:updateSize>
> -            <em:updateHash>sha256:816a7c43b4f1ea4769c61c03ea4..</em:updateHash>
> -        </RDF:Description>
> -    </em:targetApplication>
> -</RDF:Description></RDF:RDF>
> -"""
> -
> -import logging
> -from xml.etree.ElementTree import XML
> -import traceback
> -
> -import gio
> -
> -from sugar.bundle.bundleversion import NormalizedVersion
> -from sugar.bundle.bundleversion import InvalidVersionError
> -
> -from jarabe import config
> -
> -_FIND_DESCRIPTION = \
> -        './/{http://www.w3.org/1999/02/22-rdf-syntax-ns#}Description'
> -_FIND_VERSION = './/{http://www.mozilla.org/2004/em-rdf#}version'
> -_FIND_LINK = './/{http://www.mozilla.org/2004/em-rdf#}updateLink'
> -_FIND_SIZE = './/{http://www.mozilla.org/2004/em-rdf#}updateSize'
> -
> -_UPDATE_PATH = 'http://activities.sugarlabs.org/services/update-aslo.php'
> -
> -_fetcher = None
> -
> -
> -class _UpdateFetcher(object):
> -
> -    _CHUNK_SIZE = 10240
> -
> -    def __init__(self, bundle, completion_cb):
> -        # ASLO knows only about stable SP releases
> -        major, minor = config.version.split('.')[0:2]
> -        sp_version = '%s.%s' % (major, int(minor) + int(minor) % 2)
> -
> -        url = '%s?id=%s&appVersion=%s' % \
> -                (_UPDATE_PATH, bundle.get_bundle_id(), sp_version)
> -
> -        logging.debug('Fetch %s', url)
> -
> -        self._completion_cb = completion_cb
> -        self._file = gio.File(url)
> -        self._stream = None
> -        self._xml_data = ''
> -        self._bundle = bundle
> -
> -        self._file.read_async(self.__file_read_async_cb)
> -
> -    def __file_read_async_cb(self, gfile, result):
> -        try:
> -            self._stream = self._file.read_finish(result)
> -        except:
> -            global _fetcher
> -            _fetcher = None
> -            self._completion_cb(None, None, None, None, traceback.format_exc())
> -            return
> -
> -        self._stream.read_async(self._CHUNK_SIZE, self.__stream_read_async_cb)
> -
> -    def __stream_read_async_cb(self, stream, result):
> -        xml_data = self._stream.read_finish(result)
> -        if xml_data is None:
> -            global _fetcher
> -            _fetcher = None
> -            self._completion_cb(self._bundle, None, None, None,
> -                    'Error reading update information for %s from '
> -                    'server.' % self._bundle.get_bundle_id())
> -            return
> -        elif not xml_data:
> -            self._process_result()
> -        else:
> -            self._xml_data += xml_data
> -            self._stream.read_async(self._CHUNK_SIZE,
> -                                    self.__stream_read_async_cb)
> -
> -    def _process_result(self):
> -        document = XML(self._xml_data)
> -
> -        if document.find(_FIND_DESCRIPTION) is None:
> -            logging.debug('Bundle %s not available in the server for the '
> -                'version %s', self._bundle.get_bundle_id(), config.version)
> -            version = None
> -            link = None
> -            size = None
> -        else:
> -            try:
> -                version = NormalizedVersion(document.find(_FIND_VERSION).text)
> -            except InvalidVersionError:
> -                logging.exception('Exception occured while parsing version')
> -                version = '0'
> -
> -            link = document.find(_FIND_LINK).text
> -
> -            try:
> -                size = long(document.find(_FIND_SIZE).text) * 1024
> -            except ValueError:
> -                logging.exception('Exception occured while parsing size')
> -                size = 0
> -
> -        global _fetcher
> -        _fetcher = None
> -        self._completion_cb(self._bundle, version, link, size, None)
> -
> -
> -def fetch_update_info(bundle, completion_cb):
> -    """Queries the server for a newer version of the ActivityBundle.
> -
> -       completion_cb receives bundle, version, link, size and possibly an error
> -       message:
> -
> -       def completion_cb(bundle, version, link, size, error_message):
> -    """
> -    global _fetcher
> -
> -    if _fetcher is not None:
> -        raise RuntimeError('Multiple simultaneous requests are not supported')
> -
> -    _fetcher = _UpdateFetcher(bundle, completion_cb)
> diff --git a/extensions/cpsection/updater/backends/microformat.py b/extensions/cpsection/updater/backends/microformat.py
> new file mode 100644
> index 0000000..e9b6bd6
> --- /dev/null
> +++ b/extensions/cpsection/updater/backends/microformat.py
> @@ -0,0 +1,202 @@
> +#!/usr/bin/python
> +#
> +# Copyright (C) 2011, Anish Mangal <anish at sugarlabs.org>
> +#
> +# 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 2 of the License, 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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
> +
> +import logging
> +from HTMLParser import HTMLParser
> +import urllib
> +import re
> +
> +import gio
> +import gobject
> +
> +from jarabe import config
> +
> +#_UPDATE_PATH = 'http://activities-testing.sugarlabs.org/services/micro-format.php?collection_nickname=fructose'
> +_UPDATE_PATH = 'http://wiki.paraguayeduca.org/index.php/Actividades_Dextrose_1'
> +_ACTIVITIES_LIST = {}
> +ACTION_CHECKING = 0
> +ACTION_UPDATING = 1
> +ACTION_DOWNLOADING = 2
> +
> +class MicroformatParser(HTMLParser):
> +
> +    def __init__(self, data, completion_cb):
> +        HTMLParser.__init__(self)
> +        self.reset()
> +        self._data_to_parse = data
> +        self._activity_id = ''
> +        self._activity_url = ''
> +        self._activity_version = ''
> +        self._activity_size = 1
> +        self._activity_name = ''
> +        self._inside_activity_block = False
> +        self._inside_activity_version = False
> +        self._inside_activity_id = False
> +        self._inside_activity_url = False
> +        self._inside_activity_size = False
> +        self._inside_activity_name = False
> +        self._activity_block_tag = ''
> +        self._completion_cb = completion_cb
> +
> +    def parse(self):
> +        self.feed(self._data_to_parse)
> +
> +    def handle_endtag(self, tag):
> +        if tag == self._activity_block_tag and self._inside_activity_block:
> +            self._inside_activity_block = False
> +
> +            _ACTIVITIES_LIST[self._activity_id] = \
> +                    {'version':self._activity_version,
> +                            'url':self._activity_url,
> +                            'size':self._activity_size,
> +                            'name':self._activity_name}
> +
> +        elif tag == 'a':
> +            if self._inside_activity_url:
> +                self._inside_activity_url = False
> +
> +        elif tag == 'body':
> +            num_bundles = len(_ACTIVITIES_LIST)
> +            progress = num_bundles
> +            for bundle, info in _ACTIVITIES_LIST.items():
> +                progress = progress + 1
> +                if _ACTIVITIES_LIST[bundle]['size'] == 1:
> +                    try:
> +                        _ACTIVITIES_LIST[bundle]['size'] = \
> +                            gio.File(_ACTIVITIES_LIST[bundle]['url']).\
> +                            query_info('*').get_size()
> +
> +                    except Exception, e:
> +                        logging.exception(e)
> +
> +                    if _ACTIVITIES_LIST[bundle]['size'] == 0:
> +                        logging.error('Size of activity %s reported as '
> +                                '0 bytes. Excluding from update list' % bundle)
> +                        del _ACTIVITIES_LIST[bundle]
> +
> +                    elif _ACTIVITIES_LIST[bundle]['name'] == '':
> +                        # Do some regex magic to get the 'probable'
> +                        # activity name.
> +                        activity_name = re.split('\.',
> +                                bundle)[-1]
> +                        activity_name = re.sub('^[\s|\t]*', '',
> +                                activity_name)
> +                        activity_name = re.sub('[\s|\t]*$', '',
> +                                activity_name)
> +                        activity_name = re.sub('[A|a]ctivity$', '',
> +                                activity_name)
> +                        _ACTIVITIES_LIST[bundle]['name'] = \
> +                                activity_name
> +
> +            self._completion_cb(_ACTIVITIES_LIST, None)
> +
> +    def handle_starttag(self, tag, attrs):
> +        for attribute, value in attrs:
> +            if value == 'olpc-activity-info':
> +                self._inside_activity_block = True
> +                self._activity_block_tag = tag
> +
> +        if tag == 'span':
> +            for attribute, value in attrs:
> +                if value == 'olpc-activity-id':
> +                    self._inside_activity_id = True
> +                elif value == 'olpc-activity-version':
> +                    self._inside_activity_version = True
> +                elif value == 'olpc-activity-name':
> +                    self._inside_activity_name = True
> +                elif value == 'olpc-activity-size':
> +                    self._inside_activity_size = True
> +                elif value == 'olpc-activity-url':
> +                    self._inside_activity_url = True
> +
> +        elif tag == 'a':
> +            if self._inside_activity_url:
> +                for attribute, value in attrs:
> +                    if attribute == 'href':
> +                        self._activity_url = value
> +
> +    def handle_data(self, data):
> +        if self._inside_activity_version:
> +            self._activity_version = int(data)
> +            self._inside_activity_version = False
> +
> +        elif self._inside_activity_id:
> +            self._activity_id = data
> +            self._inside_activity_id = False
> +
> +        elif self._inside_activity_name:
> +            self._activity_name = data
> +            self._inside_activity_name = False
> +
> +        elif self._inside_activity_size:
> +            self._activity_size = int(data)
> +            self._inside_activity_size = False
> +
> +class _UpdateFetcher(gobject.GObject):
> +
> +    __gsignals__ = {
> +        'progress': (gobject.SIGNAL_RUN_FIRST,
> +                     gobject.TYPE_NONE,
> +                     ([int, str, float, int])),
> +    }
> +
> +    def __init__(self, completion_cb):
> +        gobject.GObject.__init__(self)
> +        # ASLO knows only about stable SP releases
> +        major, minor = config.version.split('.')[0:2]
> +        sp_version = '%s.%s' % (major, int(minor) + int(minor) % 2)
> +        self._data = ''
> +        self._completion_cb = completion_cb
> +
> +    def download_bundle_updates(self):
> +        self.emit('progress', ACTION_CHECKING, 'Fetching update '
> +                'information', 1, 3)
> +        self._url = _UPDATE_PATH
> +        self._file = gio.File(self._url)
> +        logging.debug('Fetch %s', self._url)
> +        self._file.read_async(self.__read_async_cb)
> +
> +    def __read_async_cb(self, gfile, result):
> +        try:
> +            stream = gfile.read_finish(result)
> +        except gio.Error, e:
> +            self.stop()
> +            logging.exception('Error while fetching content from %s' %
> +                    self._url)
> +            return
> +        stream.read_async(4096, self.__stream_read_cb)
> +
> +    def __stream_read_cb(self, stream, result):
> +        data = stream.read_finish(result)
> +        if not data:
> +            self._data_finished()
> +            return
> +        self._data_read(data)
> +        stream.read_async(4096, self.__stream_read_cb)
> +
> +    def _data_read(self, data):
> +        self._data += data
> +
> +    def read_finish(self):
> +        pass
> +
> +    def _data_finished(self):
> +        self.emit('progress', ACTION_CHECKING, 'Fetching update '
> +                'information', 2, 3)
> +        parser = MicroformatParser(self._data, self._completion_cb)
> +        gobject.idle_add(parser.parse)
> diff --git a/extensions/cpsection/updater/model.py b/extensions/cpsection/updater/model.py
> old mode 100755
> new mode 100644
> index 77cc873..e0b688f
> --- a/extensions/cpsection/updater/model.py
> +++ b/extensions/cpsection/updater/model.py
> @@ -37,8 +37,26 @@ from sugar.bundle.bundleversion import NormalizedVersion
>
>  from jarabe.model import bundleregistry
>
> -from backends import aslo
> +from backends import microformat
>
> +class MetaBundle():
> +
> +    def __init__(self, bundle_id, version, name):
> +        self._bundle_id = bundle_id
> +        self._version = version
> +        self._name = name
> +
> +    def get_name(self):
> +        return self._name
> +
> +    def get_bundle_id(self):
> +        return self._bundle_id
> +
> +    def get_icon(self):
> +        pass
> +
> +    def get_activity_version(self):
> +        return self._version
>
>  class UpdateModel(gobject.GObject):
>     __gtype_name__ = 'SugarUpdateModel'
> @@ -63,43 +81,52 @@ class UpdateModel(gobject.GObject):
>         self._downloader = None
>         self._cancelling = False
>
> +    def __progress_cb(self, model, action, description, current, total):
> +        self.emit('progress', action, description, current, total)
> +
>     def check_updates(self):
>         self.updates = []
> +        self._current_bundles = {}
> +        for bundle in bundleregistry.get_registry():
> +            self._current_bundles[bundle.get_bundle_id()] =\
> +                    {'version':bundle.get_activity_version(),
> +                            'bundle': bundle}
>         self._bundles_to_check = \
>                 [bundle for bundle in bundleregistry.get_registry()]
> -        self._check_next_update()
> -
> -    def _check_next_update(self):
> -        total = len(bundleregistry.get_registry())
> -        current = total - len(self._bundles_to_check)
> -
> -        bundle = self._bundles_to_check.pop()
> -        self.emit('progress', UpdateModel.ACTION_CHECKING, bundle.get_name(),
> -                  current, total)
> +        self._fetcher = microformat._UpdateFetcher(self.__bundle_info_fetched_cb)
> +        self._fetcher.connect('progress', self.__progress_cb)
> +        gobject.idle_add(self._fetcher.download_bundle_updates)
>
> -        aslo.fetch_update_info(bundle, self.__check_completed_cb)
> -
> -    def __check_completed_cb(self, bundle, version, link, size, error_message):
> +    def __bundle_info_fetched_cb(self, new_bundles, error_message):
>         if error_message is not None:
>             logging.error('Error getting update information from server:\n'
>                           '%s' % error_message)
>
> -        if version is not None and \
> -                version > NormalizedVersion(bundle.get_activity_version()):
> -            self.updates.append(BundleUpdate(bundle, version, link, size))
> -
>         if self._cancelling:
>             self._cancel_checking()
> -        elif self._bundles_to_check:
> -            gobject.idle_add(self._check_next_update)
>         else:
> -            total = len(bundleregistry.get_registry())
> -            if bundle is None:
> -                name = ''
> -            else:
> -                name = bundle.get_name()
> -            self.emit('progress', UpdateModel.ACTION_CHECKING, name, total,
> -                      total)
> +            for bundle_id, info in new_bundles.items():
> +                if bundle_id in self._current_bundles:
> +                    if new_bundles[bundle_id]['version'] >\
> +                        self._current_bundles[bundle_id]['version']:
> +                        self.updates.append(BundleUpdate(
> +                            self._current_bundles[bundle_id]['bundle'],
> +                            new_bundles[bundle_id]['version'],
> +                            new_bundles[bundle_id]['url'],
> +                            new_bundles[bundle_id]['size'],
> +                            'update'))
> +                else:
> +                    bundle = MetaBundle(bundle_id,
> +                            new_bundles[bundle_id]['version'],
> +                            new_bundles[bundle_id]['name'])
> +                    self.updates.append(BundleUpdate(bundle,
> +                        new_bundles[bundle_id]['version'],
> +                        new_bundles[bundle_id]['url'],
> +                        new_bundles[bundle_id]['size'],
> +                        'new'))
> +
> +        self.emit('progress', UpdateModel.ACTION_CHECKING, 'Fetching update '
> +                'information', 3, 3)
>
>     def update(self, bundle_ids):
>         self._bundles_to_update = []
> @@ -224,12 +251,14 @@ class UpdateModel(gobject.GObject):
>
>  class BundleUpdate(object):
>
> -    def __init__(self, bundle, version, link, size):
> +    def __init__(self, bundle, version, link, size, package_type = None):
>         self.bundle = bundle
>         self.version = version
>         self.link = link
>         self.size = size
> -
> +        # Specify whether installing a new bundle or updating an
> +        # existing one
> +        self.package_type = package_type
>
>  class _Downloader(gobject.GObject):
>     _CHUNK_SIZE = 10240  # 10K
> diff --git a/extensions/cpsection/updater/view.py b/extensions/cpsection/updater/view.py
> index 814658f..30875e4 100644
> --- a/extensions/cpsection/updater/view.py
> +++ b/extensions/cpsection/updater/view.py
> @@ -122,7 +122,7 @@ class ActivityUpdater(SectionView):
>             return
>
>         if action == UpdateModel.ACTION_CHECKING:
> -            message = _('Checking %s...') % bundle_name
> +            message = _('%s...') % bundle_name
>         elif action == UpdateModel.ACTION_DOWNLOADING:
>             message = _('Downloading %s...') % bundle_name
>         elif action == UpdateModel.ACTION_UPDATING:
> @@ -361,11 +361,17 @@ class UpdateListModel(gtk.ListStore):
>             row[self.SELECTED] = True
>             row[self.ICON_FILE_NAME] = bundle_update.bundle.get_icon()
>
> -            details = _('From version %(current)s to %(new)s (Size: %(size)s)')
> -            details = details % \
> -                    {'current': bundle_update.bundle.get_activity_version(),
> -                     'new': bundle_update.version,
> -                     'size': _format_size(bundle_update.size)}
> +            if bundle_update.package_type == 'update':
> +                details = _('From version %(current)d to %(new)s (Size: %(size)s)')
> +                details = details % \
> +                        {'current': bundle_update.bundle.get_activity_version(),
> +                         'new': bundle_update.version,
> +                         'size': _format_size(bundle_update.size)}
> +            elif bundle_update.package_type == 'new':
> +                details = _('Install new activity version %(new)s (Size: %(size)s)')
> +                details = details % \
> +                        {'new': bundle_update.version,
> +                         'size': _format_size(bundle_update.size)}
>
>             row[self.DESCRIPTION] = '<b>%s</b>\n%s' % \
>                     (bundle_update.bundle.get_name(), details)
> --
> 1.7.2.3
>
>



-- 
Anish


More information about the Dextrose mailing list