implement non-blocking locking for the estdb, and our own locking for the autoload statetmpfile... it should work well now
"""
Handling user preferences
"""
import functools
import Cookie
from qmsk.web import urltree
class Preference (urltree.URLType) :
"""
A specific preference
"""
# the name to use
name = None
# the default value
default = None
def process (self, preferences, value) :
"""
Post-process this preference value. This can access the values of other preferences or the request via
the given RequestPreferences. Note that the other items will not be post-processed here.
Defaults to just return value.
"""
return value
class RequestPreferences (object) :
"""
Represents the specific preferences for some request
"""
def __init__ (self, preferences, request, value_map=None) :
"""
Initialize with the given Preferences object, http Request, and list of mapping of raw preference values
"""
# store
self.preferences = preferences
self.request = request
# initialize
self.values = {}
self.set_cookies = {}
# load preferences
for pref in preferences.pref_list :
# got a value for it?
if value_map and pref.name in value_map :
# get value
value = value_map[pref.name]
# parse it
value = pref.parse(value)
else :
# use default value
value = pref.default
# add
self.values[pref.name] = value
# then post-process using the Preferences
self.values = dict(
(name, self.preferences.pref_map[name].process(self, value)) for name, value in self.values.iteritems()
)
def get (self, pref) :
"""
Return the value for the given Preference, or preference name
"""
# Preference -> name
if isinstance(pref, Preference) :
pref = pref.name
# look up
return self.values[pref]
# support dict-access
__getitem__ = get
def set (self, name, value_obj) :
"""
Set a new value for the given preference
"""
# sanity-check to make sure we're not setting it twice...
assert name not in self.set_cookies
# encode using the Preference object
value_str = self.preferences.pref_map[name].build(value_obj)
# update in our dict
self.values[name] = value_obj
# add to set_cookies
self.set_cookies[name] = value_str
class Preferences (object) :
"""
Handle user preferences using cookies
"""
def __init__ (self, pref_list) :
"""
Use the given list of Preference objects
"""
# store
self.pref_list = pref_list
# translate to mapping as well
self.pref_map = dict((pref.name, pref) for pref in pref_list)
def load (self, request, ) :
"""
Load the set of preferences for the given request, and return as a { name -> value } dict
"""
# the dict of values
values = {}
# load the cookies
cookie_data = request.env.get('HTTP_COOKIE')
# got any?
if cookie_data :
# parse into a SimpleCookie
cookies = Cookie.SimpleCookie(cookie_data)
# update the the values
values.update((morsel.key, morsel.value) for morsel in cookies.itervalues())
else :
cookies = None
# apply any query parameters
for pref in self.pref_list :
# look for a query param
value = request.get_arg(pref.name)
if value :
# override
values[pref.name] = value
# build the RequestPreferences object
return cookies, RequestPreferences(self, request, values)
def handler (self, *pref_list) :
"""
Intended to be used as a decorator for a request handler, this will load the give Preferences and pass
them to the wrapped handler as keyword arguments, in addition to any others given.
"""
def _decorator (func) :
@functools.wraps(func)
def _handler (request, **args) :
# load preferences
cookies, prefs = self.load(request)
# bind to request.prefs
# XXX: better way to do this? :/
request.prefs = prefs
# update args with new ones
args.update(((pref.name, prefs.get(pref)) for pref in pref_list))
# handle to get response
response = func(request, **args)
# set cookies?
if prefs.set_cookies :
if not cookies :
cookies = Cookie.SimpleCookie('')
# update cookies
for key, value in prefs.set_cookies.iteritems() :
cookies[key] = value
# add headers
for morsel in cookies.itervalues() :
response.add_header('Set-cookie', morsel.OutputString())
return response
# return wrapped handler
return _handler
# return decorator...
return _decorator
# now for our defined preferences....
import pytz
import config
class TimeFormat (urltree.URLStringType, Preference) :
"""
Time format
"""
# set name
name = 'time_format'
# default value
default = config.PREF_TIME_FMT_DEFAULT
class DateFormat (urltree.URLStringType, Preference) :
"""
Date format
"""
# set name
name = 'date_format'
# default value
default = config.PREF_DATE_FMT_DEFAULT
class Timezone (Preference) :
"""
Timezone
"""
# set name
name = 'timezone'
# default value is UTC...
default = config.PREF_TIMEZONE_DEFAULT
def parse (self, name) :
"""
tz_name -> pytz.timezone
"""
return pytz.timezone(name)
def build (self, tz) :
"""
pytz.timezone -> tz_name
"""
return tz.zone
class ImageFont (Preference) :
"""
Font for ImageFormatter
"""
# set name
name = 'image_font'
def __init__ (self, font_dict, default_name) :
"""
Use the given { name: (path, title) } dict and default the given name
"""
self.font_dict = font_dict
self.default = self.parse(default_name)
def parse (self, name) :
"""
name -> (name, path, title)
"""
path, title = self.font_dict[name]
return name, path, title
def build (self, font_info) :
"""
(name, path, title) -> name
"""
name, path, title = font_info
return name
class ImageFontSize (urltree.URLIntegerType, Preference) :
# set name, default
name = 'image_font_size'
default = config.PREF_IMAGE_FONT_SIZE_DEFAULT
# XXX: params
class Formatter (Preference) :
"""
LogFormatter to use
"""
# set name
name = 'formatter'
def __init__ (self, formatters, default) :
"""
Use the given { name -> class LogFormatter } dict and default (a LogFormatter class)
"""
self.formatters = formatters
self.default = default
def parse (self, fmt_name) :
"""
fmt_name -> class LogFormatter
"""
return self.formatters[fmt_name]
def build (self, fmt_cls) :
"""
class LogFormatter -> fmt_name
"""
return fmt_cls.name
def process (self, prefs, fmt_cls) :
"""
class LogFormatter -> LogFormatter(tz, time_fmt, image_font.path)
"""
font_name, font_path, font_title = prefs[image_font]
font_size = prefs[image_font_size]
return fmt_cls(prefs[timezone], prefs[time_format], font_path, font_size)
class Count (urltree.URLIntegerType, Preference) :
"""
Number of lines of log data to display per page
"""
# set name
name = "count"
# default
default = config.PREF_COUNT_DEFAULT
def __init__ (self) :
super(Count, self).__init__(allow_negative=False, allow_zero=False, max=config.PREF_COUNT_MAX)
# and then build the Preferences object
time_format = TimeFormat()
date_format = DateFormat()
timezone = Timezone()
image_font = ImageFont(config.FORMATTER_IMAGE_FONTS, config.PREF_IMAGE_FONT_DEFAULT)
image_font_size = ImageFontSize()
formatter = Formatter(config.LOG_FORMATTERS, config.PREF_FORMATTER_DEFAULT)
count = Count()
preferences = Preferences([
time_format,
date_format,
timezone,
image_font,
image_font_size,
formatter,
count,
])