[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