X-Git-Url: http://git.veekun.com/zzz-spline-frontpage.git/blobdiff_plain/f644131f6f14f3851da390399f93b8fbe0f800e6..9be4bb2b3da075385c748f33488d316ebcbdd9ff:/splinext/frontpage/controllers/frontpage.py diff --git a/splinext/frontpage/controllers/frontpage.py b/splinext/frontpage/controllers/frontpage.py index 4bac44f..04386fd 100644 --- a/splinext/frontpage/controllers/frontpage.py +++ b/splinext/frontpage/controllers/frontpage.py @@ -1,4 +1,7 @@ +from collections import defaultdict +import datetime import logging +import re from pylons import config, request, response, session, tmpl_context as c, url from pylons.controllers.util import abort, redirect_to @@ -16,18 +19,102 @@ class FrontPageController(BaseController): def index(self): """Magicaltastic front page. - Plugins can register things to appear on it, somehow. + Plugins can register a hook called 'frontpage_updates_' to add + updates to the front page. `` is an arbitrary string indicating + the sort of update the plugin knows how to handle; for example, + spline-forum has a `frontpage_updates_forum` hook for posting news from + a specific forum. + + Hook handlers should return a list of FrontPageUpdate objects. + + Standard hook parameters are: + `limit`, the maximum number of items that should ever be returned. + `max_age`, the number of seconds after which items expire. + `title`, a name for the source. + `icon`, an icon to show next to its name. + + `limit` and `max_age` are also global options. + + Updates are configured in the .ini like so: + + spline-frontpage.sources.foo = updatetype + spline-frontpage.sources.foo.opt1 = val1 + spline-frontpage.sources.foo.opt2 = val2 + + Note that the 'foo' name is completely arbitrary and is only used for + grouping options together. This will result in a call to: + + run_hooks('frontpage_updates_updatetype', opt1=val1, opt2=val2) Local plugins can override the fairly simple index.mako template to customize the front page layout. """ - # Hooks should return a list of FrontPageUpdate objects, making this - # return value a list of lists - updates_lol = run_hooks('frontpage_updates', limit=10) - updates = sum(updates_lol, []) - - updates.sort(key=lambda obj: obj.time) - updates.reverse() - c.updates = updates[0:10] + # XXX no reason to do this on the fly; cache it on server startup + update_config = defaultdict(dict) # source_name => config + key_rx = re.compile( + '(?x) ^ spline-frontpage [.] sources [.] (\w+) (?: [.] (\w+) )? $') + for key, val in config.iteritems(): + match = key_rx.match(key) + if not match: + continue + + source_name, subkey = match.groups() + if not subkey: + # This is the type declaration; use a special key + subkey = '__type__' + + if subkey in ('limit', 'max_age'): + val = int(val) + update_config[source_name][subkey] = val + + global_limit = int(config.get('spline-frontpage.limit', 10)) + now = datetime.datetime.now() + try: + global_max_age = now - datetime.timedelta( + seconds=int(config['spline-frontpage.max_age'])) + except KeyError: + global_max_age = None + + # Ask plugins to deal with this stuff for us! + updates = [] + for source, source_config in update_config.iteritems(): + hook_name = 'frontpage_updates_' + source_config['__type__'] + + # Merge with the global config + merged_config = source_config.copy() + del merged_config['__type__'] + + merged_config['limit'] = min( + merged_config.get('limit', global_limit), + global_limit, + ) + + try: + local_max_age = now - datetime.timedelta( + seconds=merged_config['max_age']) + except KeyError: + local_max_age = None + + if global_max_age and local_max_age: + merged_config['max_age'] = max(global_max_age, local_max_age) + else: + merged_config['max_age'] = global_max_age or local_max_age + + # XXX bleh + updates_lol = run_hooks(hook_name, **merged_config) + source_obj = updates_lol[0] + updates += source_obj.poll(merged_config['limit'], merged_config['max_age']) + + # Little optimization: maximum age effectively becomes the age of + # the oldest thing that would still appear on the page, as anything + # older would drop off the end no matter what. + # So sort by descending time and crop each iteration... + updates.sort(key=lambda obj: obj.time, reverse=True) + updates = updates[:global_limit] + + if updates and len(updates) == global_limit: + global_max_age = updates[-1].time + + c.updates = updates return render('/index.mako')