X-Git-Url: http://git.veekun.com/zzz-spline-frontpage.git/blobdiff_plain/4371e472c2c75a12b5e1e499a9df6e62d778c030..refs/tags/veekun-promotions/2011030302:/splinext/frontpage/controllers/frontpage.py diff --git a/splinext/frontpage/controllers/frontpage.py b/splinext/frontpage/controllers/frontpage.py index 478c367..fdb0b4c 100644 --- a/splinext/frontpage/controllers/frontpage.py +++ b/splinext/frontpage/controllers/frontpage.py @@ -1,15 +1,16 @@ -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 +from pylons.controllers.util import abort, redirect from routes import request_config from sqlalchemy.orm.exc import NoResultFound from spline.lib import helpers as h from spline.lib.base import BaseController, render from spline.lib.plugin.load import run_hooks +from spline.model import meta +from splinext.frontpage.sources import max_age_to_datetime log = logging.getLogger(__name__) @@ -26,8 +27,13 @@ class FrontPageController(BaseController): Hook handlers should return a list of FrontPageUpdate objects. - Standard hook parameters are `limit`, the maximum number of items that - should ever be returned. + 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: @@ -40,49 +46,76 @@ class FrontPageController(BaseController): run_hooks('frontpage_updates_updatetype', opt1=val1, opt2=val2) - Standard options are not shown and take precedence over whatever's in - the config file. + Plugins may also respond to the `frontpage_extras` hook with other + interesting things to put on the front page. There's no way to + customize the order of these extras or which appear and which don't, at + the moment. Such hooks should return an object with at least a + `template` attribute; the template will be called with the object + passed in as its `obj` argument. Local plugins can override the fairly simple index.mako template to customize the front page layout. """ - # 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__' - - update_config[source_name][subkey] = val - - - global_config = dict( - limit = 10, - ) - # Ask plugins to deal with this stuff for us! updates = [] - for source, source_config in update_config.iteritems(): - source_config2 = source_config.copy() - hook_name = 'frontpage_updates_' + source_config2.pop('__type__') - source_config2.update(global_config) - - # Hooks should return a list of FrontPageUpdate objects, making this - # return value a list of lists - updates_lol = run_hooks(hook_name, **source_config2) - updates += sum(updates_lol, []) - - # Sort everything by descending time, then crop to the right number of - # items - updates.sort(key=lambda obj: obj.time) - updates.reverse() - c.updates = updates[:global_config['limit']] - - return render('/index.mako') + global_limit = config['spline-frontpage.limit'] + global_max_age = max_age_to_datetime( + config['spline-frontpage.max_age']) + + c.sources = config['spline-frontpage.sources'] + for source in c.sources: + new_updates = source.poll(global_limit, global_max_age) + updates.extend(new_updates) + + # Little optimization: once there are global_limit items, anything + # older than the oldest cannot possibly make it onto the list. So, + # bump global_max_age to that oldest time if this is ever the case. + updates.sort(key=lambda obj: obj.time, reverse=True) + del updates[global_limit:] + + if updates and len(updates) == global_limit: + global_max_age = updates[-1].time + + # Find the oldest unseen item, to draw a divider after it. + # If this stays as None, the divider goes at the top + c.last_seen_item = None + # Could have a timestamp in the stash if this is a user, or in a cookie + # if this session has ever been logged out... + times = [] + for source in (c.user.stash, request.cookies): + try: + times.append( int(source['frontpage-last-seen-time']) ) + except (KeyError, ValueError): + pass + + if times: + last_seen_time = datetime.datetime.fromtimestamp(max(times)) + for update in updates: + if update.time > last_seen_time: + c.last_seen_item = update + else: + break + + # Save ~now~ as the last-seen time + now = datetime.datetime.now().strftime('%s') + if c.user: + c.user.stash['frontpage-last-seen-time'] = now + meta.Session.add(c.user) + else: + response.set_cookie('frontpage-last-seen-time', now) + + # Done! Feed to template + c.updates = updates + + # Hook for non-update interesting things to put on the front page. + # This hook should return objects with a 'template' attribute, and + # whatever else they need + c.extras = run_hooks('frontpage_extras') + + ret = render('/index.mako') + + # Commit AFTER rendering the template! Committing invalidates + # everything in the session, undoing any eagerloading that may have + # been done by sources + meta.Session.commit() + return ret