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 _______________________________________________ Server-devel mailing list server-de...@lists.laptop.org http://lists.laptop.org/listinfo/server-devel