Files
twitter-project/eca/sessions.py

129 lines
3.9 KiB
Python

from http.cookies import SimpleCookie
from collections import namedtuple
from collections.abc import Mapping
from itertools import product, chain
import time
import random
from . import httpd
from . import Context, context_activate
# Name generation for contexts and sessions
def name_parts():
"""
This generator will create an endless list of steadily increasing
name part lists.
"""
# name parts
letters = ['alpha', 'beta', 'gamma', 'delta', 'epsilon', 'zeta', 'eta',
'theta', 'iota', 'kappa', 'lambda', 'mu', 'nu', 'xi', 'omicron',
'pi', 'rho', 'sigma', 'tau', 'upsilon', 'phi', 'chi', 'psi',
'omega']
colours = ['red', 'orange', 'yellow', 'green', 'blue', 'violet']
# randomize order
random.shuffle(letters)
random.shuffle(colours)
# yield initial sequence (letter-colour)
parts = [letters, colours]
yield parts
# forever generate longer sequences by appending the letter list
# over and over. Note that this is the *same* letter list, so it will have
# the exact order.
while True:
random.shuffle(letters)
random.shuffle(colours)
parts.append(letters)
yield parts
# construct an iterator that will endlessly generate names:
# 1) for each parts list p in name_parts() we take the cartesian product
# 2) the product iterators are generated by the for...in generator
# 3) we chain these iterators so that when the first is exhausted, we can
# continue with the second, etc.
# 4) we map the function '-'.join over the list of parts from the chain
names = map('-'.join, chain.from_iterable((product(*p) for p in name_parts())))
class SessionCookie(httpd.Filter):
"""
The actual HTTP filter that will apply the cookie handling logic to each
request. This filter defers to the SessionManager with respect to the
cookie name to use and the activation of sessions.
"""
def bind(self, manager):
"""Post constructor configuration of filter."""
self.manager = manager
def handle(self):
"""
Determine if a cookie needs to be set and let the session manager
handle activation.
"""
cookies = self.request.cookies
morsel = cookies.get(self.manager.cookie)
if not morsel:
# Determine new cookie
value = self.manager.generate_name()
# Set new cookie
cookies[self.manager.cookie] = value
cookies[self.manager.cookie]['path'] = '/'
# Send the new cookie as header
self.request.send_header('Set-Cookie', cookies[self.manager.cookie].OutputString())
else:
value = morsel.value
self.manager.activate(value)
class Session:
"""
The Session bookkeeping data.
"""
def __init__(self, context, seen):
self.context = context
self.seen = seen
def activate(self):
"""Activate the session. Updates last seen time."""
self.seen = time.time()
context_activate(self.context)
class SessionManager:
"""
The SessionManager class. This class is callable so it can be used in place
of a constructor in the configuration.
"""
def __init__(self, cookie_name):
self.sessions = {}
self.cookie = cookie_name
def __call__(self, *args, **kwargs):
handler = SessionCookie(*args, **kwargs)
handler.bind(self)
return handler
def generate_name(self):
result = next(names)
while result in self.sessions:
result = next(names)
return result
def _new_session(self, name):
result = Session(Context(name=name, init_data={'name': name}), time.time())
result.context.start()
return result
def activate(self, name):
if name not in self.sessions:
self.sessions[name] = self._new_session(name)
self.sessions[name].activate()