[Server-devel] [xs-activity-server] Support .xol library bundles

Daniel Drake dsd at laptop.org
Fri Feb 13 15:35:59 EST 2009


Generalize into a a bundle superclass and create a Content subclass for
content bundles.
---
 src/xs_activities.py |  250 +++++++++++++++++++++++++++++---------------------
 1 files changed, 146 insertions(+), 104 deletions(-)

diff --git a/src/xs_activities.py b/src/xs_activities.py
index 85d873f..186d41b 100644
--- a/src/xs_activities.py
+++ b/src/xs_activities.py
@@ -16,10 +16,6 @@ from cStringIO import StringIO
 import syslog
 from ConfigParser import SafeConfigParser
 
-#See http://wiki.laptop.org/go/Activity_bundles
-INFO_PATH = 'activity/activity.info'
-INFO_SECTION = 'Activity'
-
 TEMPLATE_DIR = '/library/xs-activity-server/templates'
 
 # how many versions before the latest are worth having around.
@@ -40,10 +36,97 @@ def log(msg, level=syslog.LOG_NOTICE):
         syslog.syslog(level, msg)
         syslog.closelog()
 
-class ActivityError(Exception):
+class BundleError(Exception):
     pass
 
-class Activity:
+class Bundle(object):
+    def __init__(self, bundle):
+        self.linfo = {}
+        self.zf = zipfile.ZipFile(bundle)
+        # The activity path will be 'Something.activity/activity/activity.info'
+        for p in self.zf.namelist():
+            if p.endswith(self.INFO_PATH):
+                self.raw_data = read_info_file(self.zf, p, self.INFO_SECTION)
+
+        # the file name itself is needed for the URL
+        self.url = os.path.basename(bundle)
+
+        self.name = self.raw_data.get('name')
+        self.license = self.raw_data.get('license', None)
+
+        # child ctor should now call
+        # _set_bundle_id
+        # _set_version
+        # _set_description
+    def _set_bundle_id(self, id):
+        if id is None:
+            raise BundleError("bad bundle: No bundle ID")
+        self.bundle_id = id
+        if self.name is None:
+            self.name = id
+
+    def _set_version(self, version):
+        self.version = version
+
+    def _set_description(self, description):
+        self.description = description
+
+    def __cmp__(self, other):
+        """Alphabetical sort (locale dependant of course)"""
+        if self.bundle_id == other.bundle_id:
+            return cmp(self.version, other.version)
+        return cmp(self.name, other.name)
+
+    def set_older_versions(self, versions):
+        """Versions should be a list of (version number, version tuples)"""
+        self.older_versions = ', '.join('<a href="%s">%s</a>' % (v.url, v.version) for v in versions)
+
+    def to_html(self, locale, template=None):
+        """Fill in the template with data approriate for the locale."""
+        if template is None:
+            template = read_template('activity', locale)
+
+        d = {'older_versions':      self.older_versions,
+             'bundle_id':           self.bundle_id,
+             'activity_version':    self.version,
+             'bundle_url':          self.url,
+             'name':                self.name,
+             'description':         self.description,
+             }
+
+        d.update(self.linfo.get(locale, {}))
+
+        if d['older_versions']:
+            d['show_older_versions'] = 'inline'
+        else:
+            d['show_older_versions'] = 'none'
+
+        return template % d
+
+    def get_name(self, locale=None):
+        return self.name
+
+class Content(Bundle):
+    INFO_PATH = "library/library.info"
+    INFO_SECTION = "Library"
+
+    def __init__(self, bundle):
+        super(Content, self).__init__(bundle)
+
+        d = self.raw_data
+        # bundle_id is often missing; service name is used instead.
+        self._set_bundle_id(d.get('global_name', None))
+        self._set_version(int(d.get('library_version', 1)))
+        self._set_description(d.get('long_name', ''))
+
+    def debug(self, force_recheck=False):
+        # FIXME: implement debug checking for content bundles
+        return {}
+
+class Activity(Bundle):
+    INFO_PATH = "activity/activity.info"
+    INFO_SECTION = "Activity"
+
     #Activities appear to be looser than RFC3066, using e.g. _ in place of -.
     linfo_re = re.compile(r'/locale/([A-Za-z]+[\w-]*)/activity.linfo$')
 
@@ -51,41 +134,23 @@ class Activity:
         """Takes a zipped .xo bundle name, returns a dictionary of its
         activity info.  Can raise a variety of exceptions, all of
         which should indicate the bundle is invalid."""
-        zf = zipfile.ZipFile(bundle)
-        self.linfo = {}
-        # The activity path will be 'Something.activity/activity/activity.info'
-        # and locale info will be Something.activity/locale/xx_XX/activity.linfo
-        for p in zf.namelist():
+        super(Activity, self).__init__(bundle)
+
+        # The locale info will be Something.activity/locale/xx_XX/activity.linfo
+        for p in self.zf.namelist():
             linfo = self.linfo_re.search(p)
             if linfo:
                 lang = canonicalise(linfo.group(1))
-                self.linfo[lang] = read_info_file(zf, p)
-            elif p.endswith(INFO_PATH):
-                self.raw_data = read_info_file(zf, p)
-
-        # the file name itself is needed for the URL
-        self.url = os.path.basename(bundle)
+                self.linfo[lang] = read_info_file(self.zf, p, self.INFO_SECTION)
 
         # Unfortunately the dictionary lacks some information, and
         # stores other bits in inconsistent ways.
 
         d = self.raw_data
         # bundle_id is often missing; service name is used instead.
-        # if neither is present, raise a KeyError
-        self.bundle_id = d.get('bundle_id', d.get('service_name'))
-        if self.bundle_id is None:
-            raise ActivityError("bad activity: No bundle_id OR service_name")
-        # sometimes activity_version and name might be missing
-        self.version = int(d.get('activity_version', 1))
-        self.name = d.get('name', self.bundle_id)
-        self.description = d.get('description', '')
-        self.license = d.get('license', None)
-
-    def __cmp__(self, other):
-        """Alphabetical sort (locale dependant of course)"""
-        if self.bundle_id == other.bundle_id:
-            return cmp(self.version, other.version)
-        return cmp(self.name, other.name)
+        self._set_bundle_id(d.get('bundle_id', d.get('service_name')))
+        self._set_version(int(d.get('activity_version', 1)))
+        self._set_description(d.get('description', ''))
 
     def debug(self, force_recheck=False):
         """Make a copy of the raw data with added bits so we can work
@@ -139,41 +204,12 @@ class Activity:
         self._debug_data = d
         return d
 
-    def set_older_versions(self, versions):
-        """Versions should be a list of (version number, version tuples)"""
-        self.older_versions = ', '.join('<a href="%s">%s</a>' % (v.url, v.version) for v in versions)
-
-
-    def to_html(self, locale, template=None):
-        """Fill in the template with data approriate for the locale."""
-        if template is None:
-            template = read_template('activity', locale)
-
-        d = {'older_versions':      self.older_versions,
-             'bundle_id':           self.bundle_id,
-             'activity_version':    self.version,
-             'bundle_url':          self.url,
-             'name':                self.name,
-             'description':         self.description,
-             }
-
-        d.update(self.linfo.get(locale, {}))
-
-        if d['older_versions']:
-            d['show_older_versions'] = 'inline'
-        else:
-            d['show_older_versions'] = 'none'
-
-        return template % d
-
-
     def get_name(self, locale):
         """Return the best guess at a name for the locale."""
         for loc in locale_search_path(locale):
             if loc in self.linfo and 'name' in self.linfo[loc]:
                 return self.linfo[loc]['name']
-        return self.name
-
+        return super(Activity, self).get_name()
 
 
 
@@ -189,49 +225,52 @@ def check_all_bundles(directory, show_all_bundles=False):
     linfo_keys = {}
     log('Checking all activities in %s\n' % directory)
     for f in os.listdir(directory):
-        if not f.endswith('.xo'):
+        if not f.endswith('.xo') and not f.endswith('.xol'):
             continue
         #log(f)
         try:
-            activity = Activity(os.path.join(directory, f))
+            if f.endswith('.xo'):
+                bundle = Activity(os.path.join(directory, f))
+            else:
+                bundle = Content(os.path.join(directory, f))
         except Exception, e:
             log("IRREDEEMABLE bundle %-25s (Error: %s)" % (f, e), syslog.LOG_WARNING)
 
         #Clump together bundles of the same ID
-        x = unique_bundles.setdefault(activity.bundle_id, [])
-        x.append(activity)
-        all_bundles.append(activity)
+        x = unique_bundles.setdefault(bundle.bundle_id, [])
+        x.append(bundle)
+        all_bundles.append(bundle)
 
     if not show_all_bundles:
         #only show the newest one of each set.
-        activities = []
+        bundles = []
         for versions in unique_bundles.values():
             versions.sort()
-            activities.append(versions[-1])
+            bundles.append(versions[-1])
 
     else:
-        activities = all_bundles
+        bundles = all_bundles
 
     licenses = {}
-    for activity in activities:
-        bid = activity.bundle_id
-        for k, v in activity.debug().iteritems():
+    for bundle in bundles:
+        bid = bundle.bundle_id
+        for k, v in bundle.debug().iteritems():
             counts[k] = counts.get(k, 0) + 1
             if k.startswith('BAD '):
                 bc = bad_contents.setdefault(k, {})
                 bc[bid] = v
-        for k, v in activity.linfo.iteritems():
+        for k, v in bundle.linfo.iteritems():
             linfo_l = all_linfo.setdefault(k, [])
-            linfo_l.append(activity)
+            linfo_l.append(bundle)
             for x in v:
                 linfo_keys[x] = linfo_keys.get(x, 0) + 1
-            if v['name'] != activity.name:
+            if v['name'] != bundle.name:
                 linfo_l = unique_linfo.setdefault(k, [])
-                linfo_l.append(activity)
+                linfo_l.append(bundle)
 
-        if activity.license:
-            lic = licenses.setdefault(activity.license, [])
-            lic.append(activity.bundle_id)
+        if bundle.license:
+            lic = licenses.setdefault(bundle.license, [])
+            lic.append(bundle.bundle_id)
 
     citems = counts.items()
     rare_keys = [k for k, v in citems if v < 10]
@@ -247,7 +286,7 @@ def check_all_bundles(directory, show_all_bundles=False):
     linfo_counts = dict((k, len(v)) for k, v in all_linfo.iteritems())
     linfo_uniq_counts = dict((k, len(v)) for k, v in unique_linfo.iteritems())
 
-    log('\nFound: %s bundles\n       %s activities' % (len(all_bundles), len(unique_bundles)))
+    log('\nFound: %s bundles\n       %s unique bundles' % (len(all_bundles), len(unique_bundles)))
     for d, name, d2 in [(tag_counts, '\nattribute counts:', tag_quality),
                         (lack_counts, '\nmissing required keys:', {}),
                         (no_counts, '\nunused optional keys:', {}),
@@ -266,10 +305,10 @@ def check_all_bundles(directory, show_all_bundles=False):
         if k.startswith('BAD '):
             continue
         log(k)
-        for a in activities:
-            v = a.debug().get(k)
+        for b in bundles:
+            v = b.debug().get(k)
             if v:
-                log('      %-25s %s' % (a.bundle_id, v))
+                log('      %-25s %s' % (b.bundle_id, v))
 
 
     log("\nInteresting contents:")
@@ -301,32 +340,32 @@ def check_all_bundles(directory, show_all_bundles=False):
 
 
     log("\nAlmost valid activities:")
-    for a in activities:
-        d = a.debug()
+    for b in bundles:
+        d = b.debug()
         if d['MISSING KEYS'] == 1:
             missing = ', '.join(x for x in d if x.startswith('LACKS'))
             bad_values = ', '. join(x for x in d if x.startswith('BAD'))
-            log("%-20s %s %s" %(a.name, missing, bad_values))
+            log("%-20s %s %s" %(b.name, missing, bad_values))
 
     log("\nValid activities (maybe):")
-    for a in activities:
-        d = a.debug()
-        bid = a.bundle_id
+    for b in bundles:
+        d = b.debug()
+        bid = b.bundle_id
         if (d['MISSING KEYS'] == 0 and
             bid not in bad_contents['BAD mime_types']):
-            log("%-20s - %s" %(a.name, bid))
+            log("%-20s - %s" %(b.name, bid))
             #log(a.raw_data)
 
 
 
 
-def read_info_file(zipfile, path):
+def read_info_file(zipfile, path, section):
     """Return a dictionary matching the contents of the config file at
     path in zipfile"""
     cp = SafeConfigParser()
     info = StringIO(zipfile.read(path))
     cp.readfp(info)
-    return dict(cp.items(INFO_SECTION))
+    return dict(cp.items(section))
 
 def canonicalise(lang):
     """Make all equivalent language strings the same.
@@ -381,7 +420,7 @@ def htmlise_bundles(bundle_dir, dest_html):
     #the version number to find the newest.
 
     bundles = [os.path.join(bundle_dir, x)
-               for x in os.listdir(bundle_dir) if x.endswith('.xo')]
+               for x in os.listdir(bundle_dir) if x.endswith('.xo') or x.endswith('.xol')]
 
     try:
         metadata = read_metadata(bundle_dir)
@@ -389,18 +428,21 @@ def htmlise_bundles(bundle_dir, dest_html):
         log("had trouble reading metadata: %s" % e)
         metadata = {}
 
-    all_activities = {}
-    for b in bundles:
+    all_bundles = {}
+    for filename in bundles:
         try:
-            act = Activity(b)
-            x = all_activities.setdefault(act.bundle_id, [])
-            x.append((act.version, act))
+            if filename.endswith('.xo'):
+                bundle = Activity(filename)
+            else:
+                bundle = Content(filename)
+            x = all_bundles.setdefault(bundle.bundle_id, [])
+            x.append((bundle.version, bundle))
         except Exception, e:
-            log("Couldn't find good activity info in %s (Error: %s)" % (b, e))
+            log("Couldn't find good activity/library info in %s (Error: %s)" % (filename, e))
 
     newest = []
     locales = set()
-    for versions in all_activities.values():
+    for versions in all_bundles.values():
         versions = [x[1] for x in sorted(versions)]
         # end of list is the newest; beginning of list might need deleting
         latest = versions.pop()
@@ -464,14 +506,14 @@ def read_template(name, locale):
     return s
 
 
-def make_html(activities, locale, filename):
+def make_html(bundles, locale, filename):
     """Write a microformated index for the activities in the appropriate language,
     and save it to filename."""
     page_tmpl = read_template('page', locale)
     act_tmpl = read_template('activity', locale)
 
-    #activities.sort() won't cut it.
-    schwartzian = [ (x.get_name(locale), x.to_html(locale, act_tmpl)) for x in activities ]
+    #bundles.sort() won't cut it.
+    schwartzian = [ (x.get_name(locale), x.to_html(locale, act_tmpl)) for x in bundles ]
     schwartzian.sort()
     s = page_tmpl % {'activities': '\n'.join(x[1] for x in schwartzian)}
 
-- 
1.6.1.3



More information about the Server-devel mailing list