def make_helpers(config):
    return Helpers(config) def make_map():
    """Create, configure and return the routes Mapper"""
    router = Mapper(directory=config['pylons.paths']['controllers'])
    router.minimization = False

    # main APPLICATIONS

    router.connect('local-app', '/', controller='localv1', action='app')
    router.connect('disassemble-app', '/disassemble/{slug}/{asset:.+}', controller='disassembler', action='app')
    router.connect('viewer-app', '/view/{slug}/{asset:.+}', controller='viewer', action='app')

    # application API for local only!
    with router.submapper(controller='localv1/games', path_prefix='/local/v1/games') as m:
        m.connect('games-list', '/list', action='list')
        m.connect('games-new', '/new', action='new')
        m.connect('games-details', '/details/{slug}', action='details')
        m.connect('games-sessions', '/sessions', action='sessions')

    with router.submapper(controller='localv1/edit', path_prefix='/local/v1/edit/{slug}') as m:
        m.connect('edit-overview', '', action='overview')
        m.connect('edit-load', '/load', action='load')
        m.connect('edit-save', '/save', action='save')
        m.connect('edit-delete', '/delete', action='delete')
        m.connect('edit-create-slug', '/create-slug', action='create_slug')
        m.connect('edit-directory-options', '/directory-options', action='directory_options')

    with router.submapper(controller='localv1/play', path_prefix='/local/v1/play/{slug}') as m:
        m.connect('play-versions', '', action='versions')

    with router.submapper(controller='localv1/list', path_prefix='/local/v1/list/{slug}') as m:
        m.connect('list-overview', '', action='overview')
        m.connect('list-assets', '/assets/{path:.*}',  action='assets')
        m.connect('list-files', '/files/{path:.*}', action='files')

    with router.submapper(controller='localv1/metrics', path_prefix='/local/v1/metrics/{slug}') as m:
        m.connect('metrics-overview', '', action='overview')
        m.connect('metrics-stop-recording', '/stop-recording', action='stop_recording')
        m.connect('metrics-as-csv', '/session/{timestamp}.csv', action='as_csv')
        m.connect('metrics-as-json', '/session/{timestamp}.json', action='as_json')
        m.connect('metrics-delete', '/session/{timestamp}/delete', action='delete')
        m.connect('metrics-details', '/session/{timestamp}', action='details')

    with router.submapper(controller="localv1/userdata", path_prefix='/local/v1/userdata/{slug}') as m:
        m.connect('userdata-overview', '', action='overview')
        m.connect('userdata-keys', '/{username}', action='userkeys')
        m.connect('userdata-as-text', '/{username}/{key:[A-Za-z0-9]+([\-\.][A-Za-z0-9\-]+)*}', action='as_text')

    with router.submapper(controller="localv1/deploy", path_prefix='/local/v1/deploy') as m:
        m.connect('deploy-login', '/login', action='login')
        m.connect('deploy-try-login', '/try-login', action='try_login')
        m.connect('deploy-start', '/start', action='start')
        m.connect('deploy-progress', '/progress', action='progress')
        m.connect('deploy-postupload-progress', '/postupload_progress', action='postupload_progress')
        m.connect('deploy-cancel', '/cancel', action='cancel')
        m.connect('deploy-check', '/check/{slug:[A-Za-z0-9\-]+}', action='check')

    with router.submapper(controller="localv1/user", path_prefix='/local/v1/user') as m:
        m.connect('login-user', '/login', action='login')
        m.connect('get-user', '/get', action='get_user')

    # global game API for local, hub and the gaming site
    with router.submapper(controller="apiv1/userdata", path_prefix='/api/v1/user-data') as m:
        m.connect('/{action:read|set|remove|exists}/{key:[A-Za-z0-9]+([\-\.][A-Za-z0-9\-]+)*}')

        m.connect('/remove-all', action='remove_all')
        m.connect('/read', action='read_keys')

        # for backwards compatibility
        m.connect('/get/{key:[A-Za-z0-9]+([\-\.][A-Za-z0-9\-]+)*}', action='read')
        m.connect('/get-keys', action='read_keys')

    with router.submapper(controller="apiv1/gameprofile", path_prefix='/api/v1/game-profile') as m:
        m.connect('/{action:read|set|remove}')
        # Local API for testing only
        m.connect('/remove-all/{slug:[A-Za-z0-9\-]+}', action='remove_all')

    with router.submapper(controller="apiv1/leaderboards", path_prefix='/api/v1/leaderboards') as m:
        # Leaderboards Public API
        m.connect('/read/{slug:[A-Za-z0-9\-]+}', action='read_meta')

        m.connect('/scores/read/{slug:[A-Za-z0-9\-]+}', action='read_overview')
        m.connect('/scores/read/{slug:[A-Za-z0-9\-]+}/{key:[A-Za-z0-9]+([\-\.][A-Za-z0-9]+)*}',
                  action='read_expanded')

        m.connect('/aggregates/read/{slug:[A-Za-z0-9\-]+}',
                  action='read_aggregates')

        # Leaderboards Developer API
        m.connect('/scores/set/{key:[A-Za-z0-9]+([\-\.][A-Za-z0-9]+)*}', action='set')

        # Local API for testing only
        m.connect('/scores/remove-all/{slug:[A-Za-z0-9\-]+}', action='remove_all')
        m.connect('/reset-meta-data', action='reset_meta')

    with router.submapper(controller='apiv1/games', path_prefix='/api/v1/games') as m:
        m.connect('games-create-session', '/create-session/{slug}', action='create_session')
        m.connect('games-create-session', '/create-session/{slug}/{mode:[a-z\-]+}', action='create_session')
        m.connect('games-destroy-session', '/destroy-session', action='destroy_session')

    with router.submapper(controller="apiv1/badges", path_prefix='/api/v1/badges') as m:
        # Badges Public API
        m.connect('/read/{slug:[A-Za-z0-9\-]+}', action='badges_list')

        # Badges/Progress Developer API
        m.connect('/progress/add/{slug:[A-Za-z0-9\-]+}', action='badges_user_add')
        # userbadges/list: list all badges for the logged in user
        m.connect('/progress/read/{slug:[A-Za-z0-9\-]+}', action='badges_user_list')
        # Local API for testing only
        #m.connect('/progress/remove-all/{slug:[A-Za-z0-9\-]+}', action='remove_all')

    with router.submapper(controller="apiv1/datashare", path_prefix='/api/v1/data-share') as m:
        m.connect('/create/{slug:[A-Za-z0-9\-]+}', action='create')
        m.connect('/find/{slug:[A-Za-z0-9\-]+}', action='find')
        m.connect('/join/{slug:[A-Za-z0-9\-]+}/{datashare_id:[A-Za-z0-9]+}', action='join')
        m.connect('/leave/{slug:[A-Za-z0-9\-]+}/{datashare_id:[A-Za-z0-9]+}', action='leave')
        m.connect('/set-properties/{slug:[A-Za-z0-9\-]+}/{datashare_id:[A-Za-z0-9]+}', action='set_properties')
        # Secure API (requires gameSessionId)
        m.connect('/read/{datashare_id:[A-Za-z0-9]+}', action='read')
        m.connect('/read/{datashare_id:[A-Za-z0-9]+}/{key:[A-Za-z0-9]+([\-\.][A-Za-z0-9]+)*}', action='read_key')
        m.connect('/set/{datashare_id:[A-Za-z0-9]+}/{key:[A-Za-z0-9]+([\-\.][A-Za-z0-9]+)*}', action='set_key')
        m.connect('/compare-and-set/{datashare_id:[A-Za-z0-9]+}/{key:[A-Za-z0-9]+([\-\.][A-Za-z0-9]+)*}',
                  action='compare_and_set_key')

        # Local API for testing only
        m.connect('/remove-all/{slug:[A-Za-z0-9\-]+}', action='remove_all')

    with router.submapper(controller="apiv1/profiles", path_prefix='/api/v1/profiles') as m:
        # Profiles
        m.connect("/user", action="user")
        #m.connect("/game/{slug:[A-Za-z0-9\-]+}", action="game")

    with router.submapper(controller="apiv1/gameauthtoken", path_prefix='/api/v1') as m:
        m.connect("/game-auth-token/{slug:[A-Za-z0-9\-]+}", action="game_auth_token")

    with router.submapper(controller='apiv1/gamenotifications', path_prefix='/api/v1/game-notifications') as m:
        m.connect('/usersettings/read/{slug:[A-Za-z0-9\-]+}', action='read_usersettings')
        m.connect('/usersettings/update/{slug:[A-Za-z0-9\-]+}', action='update_usersettings')

        m.connect('/keys/read/{slug:[A-Za-z0-9\-]+}', action='read_notification_keys')

        m.connect('/send-instant/{slug:[A-Za-z0-9\-]+}', action='send_instant_notification')
        m.connect('/send-delayed/{slug:[A-Za-z0-9\-]+}', action='send_delayed_notification')
        m.connect('/poll/{slug:[A-Za-z0-9\-]+}', action='poll_notifications')

        m.connect('/cancel-by-id/{slug:[A-Za-z0-9\-]+}', action='cancel_notification_by_id')
        m.connect('/cancel-by-key/{slug:[A-Za-z0-9\-]+}', action='cancel_notification_by_key')
        m.connect('/cancel-all/{slug:[A-Za-z0-9\-]+}', action='cancel_all_notifications')

        m.connect('/init-manager/{slug:[A-Za-z0-9\-]+}', action='init_manager')

    with router.submapper(controller="apiv1/multiplayer", path_prefix='/api/v1/multiplayer') as m:
        # Multiplayer public API
        with m.submapper(path_prefix='/session') as ms:
            ms.connect("/create/{slug:[A-Za-z0-9\-]+}", action="create")
            ms.connect("/join", action="join")
            ms.connect("/join-any/{slug:[A-Za-z0-9\-]+}", action="join_any")
            ms.connect("/leave", action="leave")
            ms.connect("/make-public", action="make_public")
            ms.connect("/list", action="list_all")
            ms.connect("/list/{slug:[A-Za-z0-9\-]+}", action="list")
            ms.connect("/read", action="read")

        # Multiplayer servers API
        with m.submapper(path_prefix='/server') as ms:
            ms.connect("/register", action="register")
            ms.connect("/heartbeat", action="heartbeat")
            ms.connect("/unregister", action="unregister")
            ms.connect("/leave", action="client_leave")
            ms.connect("/delete", action="delete_session")

    with router.submapper(controller="apiv1/custommetrics", path_prefix='/api/v1/custommetrics') as m:
        # Custom Metrics
        m.connect("/add-event/{slug:[A-Za-z0-9\-]+}", action="add_event")
        m.connect("/add-event-batch/{slug:[A-Za-z0-9\-]+}", action="add_event_batch")

    with router.submapper(controller="apiv1/store", path_prefix='/api/v1/store') as m:
        # Store Public API
        m.connect('/currency-list', action='get_currency_meta')
        m.connect('/items/read/{slug:[A-Za-z0-9\-]+}', action='read_meta')
        m.connect('/user/items/read/{slug:[A-Za-z0-9\-]+}', action='read_user_items')
        m.connect('/user/items/consume', action='consume_user_items')

        m.connect('/transactions/checkout', action='checkout_transaction')
        m.connect('/transactions/pay/{transaction_id:[A-Za-z0-9\-]+}', action='pay_transaction')
        m.connect('/transactions/read-status/{transaction_id:[A-Za-z0-9\-]+}', action='read_transaction_status')

        # Local API for testing only
        m.connect('/user/items/remove-all/{slug:[A-Za-z0-9\-]+}', action='remove_all')

    with router.submapper(controller="apiv1/servicestatus", path_prefix='/api/v1/service-status') as m:
        # Service status
        m.connect("/read", action="read_list")
        m.connect("/set/{service_name:[A-Za-z0-9\-]+}", action="set")
        m.connect("/poll-interval/set", action="set_poll_interval")

        m.connect("/game/read/{slug:[A-Za-z0-9\-]+}", action="read")


    return router class GzipMiddleware(object):
    """ GZip compress responses if encoding is accepted by client """

    def __init__(self, app, config):
        self.app = app
        self.compress_level = asint(config.get('gzip.compress_level', '5'))
        self.compress = set(aslist(config.get('gzip.compress', ''), ',', strip=True))
        self.do_not_compress = set(aslist(config.get('gzip.do_not_compress', ''), ',', strip=True))
        for m in self.compress | self.do_not_compress:
            if mimetypes.guess_extension(m) is None:
                LOG.warning('Unrecognised mimetype in server configuration: %s', m)
        self.cache_dir = normpath(config.get('deploy.cache_dir', None))

    def __call__(self, environ, start_response):

        # if client does not accept gzip encoding, pass the request through
        if 'gzip' not in environ.get('HTTP_ACCEPT_ENCODING', ''):
            return self.app(environ, start_response)

        # capture the response headers and setup compression
        start_response_args = {
            'level': 0
        }

        def gzip_start_response(status, headers, exc_info=None):

            # We only need compress if the status is 200, this means we're sending data back.
            if status != '200 OK':
                # If status is 304 remove dummy tags
                if status.startswith('304'):
                    headers = [item for item in headers if item[0] != 'Accept-Ranges']
                return start_response(status, headers, exc_info)

            else:
                mimetype = None
                for k, v in headers:
                    if k == 'Content-Type':
                        mimetype = v.split(';')[0].split(',')[0]
                    elif k == 'Content-Length':
                        # Don't bother with small responses because the gzip file could actually be bigger
                        if int(v) <= 256:
                            return start_response(status, headers, exc_info)

                if not mimetype:
                    # This has no mimetype.
                    LOG.warning('Response with no mimetype: %s', environ.get('PATH_INFO', ''))
                    compression_level = 0
                elif mimetype in self.do_not_compress:
                    # This is a known mimetype that we don't want to compress.
                    compression_level = 0
                elif mimetype in self.compress:
                    # This is a know mimetype that we *do* want to compress.
                    compression_level = self.compress_level
                else:
                    LOG.warning('Response with mimetype not in compression lists: %s', mimetype)
                    compression_level = 1

                if compression_level != 0:
                    # save args so we can call start_response later
                    start_response_args['status'] = status
                    start_response_args['headers'] = headers
                    start_response_args['exc_info'] = exc_info

                    start_response_args['level'] = compression_level

                    response_buffer = io.BytesIO()
                    start_response_args['buffer'] = response_buffer
                    return response_buffer.write

                else:
                    return start_response(status, headers, exc_info)

        # pass on the request
        response = self.app(environ, gzip_start_response)

        compression_level = start_response_args['level']
        if compression_level != 0:

            response_buffer = start_response_args['buffer']
            response_length = 0

            # Check if it is a game file that could be pre-compressed
            if hasattr(response, 'get_game_file_path'):
                # Ignore whatever response we got
                response_buffer.close()
                response_buffer = None

                game_file_path = response.get_full_game_file_path()
                cached_game_file_path = join(self.cache_dir, response.get_game_file_path() + '.gz')

                cached_mtime, cached_size = _get_file_stats(cached_game_file_path)
                source_mtime, source_size = _get_file_stats(game_file_path)
                if cached_mtime < source_mtime:
                    if not compress_file(game_file_path, cached_game_file_path):
                        start_response(start_response_args['status'], start_response_args['headers'])
                        return response

                    # We round mtime up to the next second to avoid precision problems with floating point values
                    source_mtime = long(source_mtime) + 1
                    os.utime(cached_game_file_path, (source_mtime, source_mtime))

                    _, cached_size = _get_file_stats(cached_game_file_path)

                if cached_size < source_size:
                    if hasattr(response, 'close'):
                        response.close()
                        response = None

                    response = GzipFileIter(open(cached_game_file_path, 'rb'))
                    response_length = cached_size

                else:
                    start_response(start_response_args['status'], start_response_args['headers'])
                    return response

            else:
                response, response_length = _compress_response(response, response_buffer, compression_level)

            # override the content-length and content-encoding response headers
            headers = [ ]
            for name, value in start_response_args['headers']:
                name_lower = name.lower()
                if name_lower != 'content-length' and name_lower.find('-range') == -1:
                    headers.append((name, value))
            headers.append(('Content-Length', str(response_length)))
            headers.append(('Content-Encoding', 'gzip'))

            start_response(start_response_args['status'], headers, start_response_args['exc_info'])

        return response class MetricsMiddleware(object):

    def __init__(self, app, config):
        self.app = app
        self.user_id_counter = int(time() - 946080000)
        self.cookie_session_name = config.get('metrics.user.key', 'metrics_id')
        self.gamelist = GameList.get_instance()

    def __call__(self, environ, start_response):
        # check whether the the request should be logged, i.e. starts with
        # 'play' and is longer than a mere request for the playable-versions
        # page
        request_path = environ.get('PATH_INFO', '')
        path_parts = request_path.strip('/').split('/', 2)

        if len(path_parts) == 3 and path_parts[0] == 'play':
            slug = path_parts[1]

            if self.gamelist.get_by_slug(slug):
                file_name = path_parts[2]

                # find user id on cookies or create a new one
                cookies = request.get_cookies(environ)
                if cookies.has_key(self.cookie_session_name):
                    user_id = cookies[self.cookie_session_name].value
                    is_new_user = False
                else:
                    self.user_id_counter += 1
                    user_id = '%x' % self.user_id_counter
                    is_new_user = True

                slug_sessions = MetricsSession.get_sessions(slug)

                # make sure there is a session when an html file is requested
                # ignore otherwise
                session = slug_sessions.get(user_id, None)
                if file_name.endswith(('.html', '.htm')) or \
                   (file_name.endswith(('.tzjs', '.canvas.js', '.swf')) and 'HTTP_REFERER' in environ and \
                    not environ['HTTP_REFERER'].endswith(('.html', '.htm'))):
                    if session:
                        session.finish()
                        session = None
                    try:
                        session = MetricsSession(slug)
                    except IOError:
                        return self.app(environ, start_response)
                    slug_sessions[user_id] = session

                elif not session:
                    return self.app(environ, start_response)

                # define function to capture status and headers from response
                response_headers = []
                def metrics_start_response(status, headers, exc_info=None):
                    if is_new_user:
                        headers.append(('Set-Cookie',
                                        '%s=%s; Path=/play/%s/' % (self.cookie_session_name, user_id, slug)))
                    response_headers.append(status)
                    response_headers.append(headers)
                    return start_response(status, headers, exc_info)

                # pass through request and get response
                response = self.app(environ, metrics_start_response)

                status = response_headers[0]
                file_size = 0
                if status.startswith('404'):
                    file_type = 'n/a'
                else:
                    file_type = None
                    if status.startswith('200'):
                        for k, v in response_headers[1]:
                            if k == 'Content-Length':
                                file_size = v
                                if file_type:
                                    break
                            elif k == 'Content-Type':
                                file_type = v
                                if file_size:
                                    break
                    else:
                        for k, v in response_headers[1]:
                            if k == 'Content-Type':
                                file_type = v
                                break
                    if not file_type:
                        file_type = mimetypes.guess_type(file_name)[0]
                        if not file_type:
                            file_type = 'n/a'
                    else:
                        file_type = file_type.split(';')[0].split(',')[0]
                session.append(file_name, file_size, file_type, status)

                # send the response back up the WSGI layers
                return response

        return self.app(environ, start_response) class LoggingMiddleware(object):
    """
    Output a message to STDOUT per response.
    """

    def __init__(self, app, config):
        self.app = app
        self.log_all_requests = asbool(config.get('logging.log_all_requests'))
        self.log_pattern = asbool(config.get('logging.log_pattern'))
        self.log_pattern_re = config.get('logging.log_pattern_re')
        self.log_request_headers = asbool(config.get('logging.log_request_headers'))
        self.log_response_name = asbool(config.get('logging.log_response_name'))
        self.log_response_headers = asbool(config.get('logging.log_response_headers'))
        self.remove_letters_re = re.compile('[^\d]')

    def __call__(self, environ, start_response):
        request_path = environ.get('PATH_INFO', '')

        # If we don't log all the requests then look at the request path to see if it is an asset request.
        # If not, send the request onto the next middleware.
        if not self.log_all_requests:
            path_parts = request_path.strip('/').split('/', 2)

            if len(path_parts) != 3 or path_parts[0] != 'play':
                return self.app(environ, start_response)

        if self.log_request_headers or self.log_response_headers:
            log_headers = (not self.log_pattern) or (re.match(self.log_pattern_re, request_path) is not None)
        else:
            log_headers = False

        if log_headers:
            if self.log_request_headers:
                LOG.info("Request Headers:")
                for k, v in environ.iteritems():
                    if k.startswith('HTTP_'):
                        LOG.info("\t%s: %s", k, v)

        # capture headers from response
        start_response_args = {}
        def logging_start_response(status, headers, exc_info=None):
            start_response_args['status'] = status
            start_response_args['headers'] = headers
            return start_response(status, headers, exc_info)

        # pass through request
        response = self.app(environ, logging_start_response)
        response_headers = dict(start_response_args['headers'])

        if self.log_response_name:
            now = datetime.now().strftime("%d/%b/%Y %H:%M:%S")
            status = start_response_args.get('status', '200')
            message = '"%s %s" %s %s' % (environ.get('REQUEST_METHOD'),
                                         request_path,
                                         self.remove_letters_re.sub('', status),
                                         response_headers.get('Content-Length', 0))
            LOG.info("[%s] %s", now, message)

        if log_headers:
            if self.log_response_headers:
                LOG.info("Response Headers:")
                for (k, v) in response_headers.iteritems():
                    LOG.info("\t%s: %s", k, v)

        return response class StaticGameFilesMiddleware(object):
    """
    Serves static files from virtual game paths mapped to real game path
    """

    def __init__(self, app, staticmax_max_age=0):
        self.app = app
        self.staticmax_max_age = staticmax_max_age
        self.cached_apps = {}
        self.game_list = GameList.get_instance()
        self.utf8_mimetypes = set(['text/html', 'application/json'])

    def __call__(self, environ, start_response):
        request_path = environ.get('PATH_INFO', '')

        # check if the request is for static files at all
        path_parts = request_path.strip('/').split('/', 2)
        if len(path_parts) == 3 and path_parts[0] in ['play', 'game-meta']:

            slug = path_parts[1]
            game = self.game_list.get_by_slug(slug)
            if game and game.path.is_set():
                asset_path = path_parts[2]
                file_asset_path = normpath(join(get_absolute_path(game.path), asset_path))

                def build_file_iter(f, block_size):
                    return StaticFileIter(file_asset_path, normpath(join(slug, asset_path)), f, block_size)

                def remove_ranges_start_response(status, headers, exc_info=None):
                    if status == '200 OK':
                        headers = [t for t in headers if t[0] != 'Accept-Ranges' and t[0] != 'Content-Range']
                    return start_response(status, headers, exc_info)

                # check if the request is already cached
                app = self.cached_apps.get(request_path)
                if app:
                    environ['wsgi.file_wrapper'] = build_file_iter

                    try:
                        return app(environ, remove_ranges_start_response)
                    except OSError as e:
                        LOG.error(e)

                elif access(file_asset_path, R_OK):
                    content_type, _ = guess_type(file_asset_path)
                    if content_type in self.utf8_mimetypes:
                        content_type += '; charset=utf-8'

                    app = FileApp(file_asset_path, content_type=content_type)

                    if asset_path.startswith('staticmax'):
                        app.cache_control(max_age=self.staticmax_max_age)
                    else:
                        app.cache_control(max_age=0)

                    self.cached_apps[request_path] = app

                    environ['wsgi.file_wrapper'] = build_file_iter
                    return app(environ, remove_ranges_start_response)

                start_response(
                    '404 Not Found',
                    [('Content-Type', 'text/html; charset=UTF-8'),
                     ('Content-Length', '0')]
                )
                return ['']

        return self.app(environ, start_response) class StaticFilesMiddleware(object):
    """
    Serves static files from virtual paths mapped to real paths
    """

    def __init__(self, app, path_items):
        self.app = app
        self.path_items = path_items
        self.cached_apps = {}
        self.utf8_mimetypes = set(['text/html', 'application/json'])

    def __call__(self, environ, start_response):

        request_path = environ.get('PATH_INFO', '')

        app = self.cached_apps.get(request_path)
        if app:
            return app(environ, start_response)

        if not request_path.endswith('/'):
            relative_request_path = request_path.lstrip('/')

            for root_dir, max_cache in self.path_items:

                file_asset_path = normcase(join(root_dir, relative_request_path))

                if isfile(file_asset_path):
                    content_type, _ = guess_type(file_asset_path)
                    if content_type in self.utf8_mimetypes:
                        content_type += '; charset=utf-8'

                    app = FileApp(file_asset_path, content_type=content_type)

                    if max_cache:
                        app.cache_control(max_age=max_cache)
                    else:
                        app.cache_control(max_age=0)

                    self.cached_apps[request_path] = app

                    return app(environ, start_response)

        return self.app(environ, start_response) class CompactMiddleware(object):

    def __init__(self, app, config):
        # pylint: disable=F0401
        from paste.deploy.converters import asbool
        # pylint: enable=F0401
        self.app = app
        self.compact_html = asbool(config.get('compact.html', True))
        self.compact_script = asbool(config.get('compact.script', True))

    @classmethod
    def disable(cls, request):
        request.environ['compact.html'] = False

    @classmethod
    def enable(cls, request):
        request.environ['compact.html'] = True

    def __call__(self, environ, start_response):
        if not self.compact_html:
            return self.app(environ, start_response)

        start_response_args = {}

        def compact_start_response(status, headers, exc_info=None):
            # We only need compress if the status is 200, this means we're sending data back.
            if status == '200 OK':
                for k, v in headers:
                    if k == 'Content-Type':
                        mimetype = v.split(';')[0].split(',')[0]
                        if mimetype == 'text/html':
                            start_response_args['compact'] = True

                            start_response_args['status'] = status
                            start_response_args['headers'] = headers
                            start_response_args['exc_info'] = exc_info

                            response_buffer = StringIO()
                            start_response_args['buffer'] = response_buffer
                            return response_buffer.write

            return start_response(status, headers, exc_info)

        # pass on the request
        response = self.app(environ, compact_start_response)

        # compact
        if start_response_args.get('compact', False):

            response_headers = start_response_args['headers']
            response_buffer = start_response_args['buffer']

            # check if we can just read the data directly from the response
            if response_buffer.tell() == 0 and \
               isinstance(response, list) and \
               len(response) == 1 and \
               isinstance(response[0], basestring):

                response_data = response[0]

            else:
                for line in response:
                    response_buffer.write(line)

                if hasattr(response, 'close'):
                    response.close()

                response_data = response_buffer.getvalue()

            response_buffer.close()

            if environ.get('compact.html', True) and HTMLMinifier:
                output = StringIO()
                compactor = HTMLMinifier(output.write, self.compact_script)
                compactor.feed(response_data)
                response_data = output.getvalue()

                headers = [ ]
                for name, value in response_headers:
                    name_lower = name.lower()
                    if name_lower != 'content-length' and name_lower.find('-range') == -1:
                        headers.append((name, value))
                headers.append(('Content-Length', str(len(response_data))))
                response_headers = headers

            response = [response_data]

            start_response(start_response_args['status'], response_headers, start_response_args['exc_info'])

        return response class EtagMiddleware(object):
    """ Add Etag header if missing """

    def __init__(self, app, config):
        self.app = app
        self.min_size = asint(config.get('etag.min_size', 1))

    def __call__(self, environ, start_response):

        if environ.get('REQUEST_METHOD') != 'GET':
            return self.app(environ, start_response)

        client_etag = environ.get('HTTP_IF_NONE_MATCH', None)

        # capture the response headers
        etag_info = {}
        start_response_args = {}
        min_size = self.min_size

        def etag_start_response(status, headers, exc_info=None):

            # We only need to calculate Etags if the status is 200
            # this means we're sending data back.
            if status != '200 OK':
                return start_response(status, headers, exc_info)

            for k, v in headers:
                if k == 'Etag':
                    if v:
                        etag_info['response_etag'] = v
                        if client_etag == v:
                            status = '304 Not Modified'
                            # Only return cookies because they may have side effects
                            headers = [item for item in headers if item[0] == 'Set-Cookie']
                            headers.append(('Etag', v))
                        return start_response(status, headers, exc_info)
                elif k == 'Content-Length':
                    # Don't bother with small responses because the Etag will actually be bigger
                    if int(v) <= min_size:
                        return start_response(status, headers, exc_info)

            # save args so we can call start_response later
            start_response_args['status'] = status
            start_response_args['headers'] = headers
            start_response_args['exc_info'] = exc_info

            response_buffer = BytesIO()

            etag_info['buffer'] = response_buffer

            return response_buffer.write

        # pass on the request
        response = self.app(environ, etag_start_response)

        # If there is a buffer then status is 200 and there is no Etag on the response
        response_buffer = etag_info.get('buffer', None)
        if response_buffer:

            # check if we can just read the data directly from the response
            if response_buffer.tell() == 0 and \
               isinstance(response, list) and \
               len(response) == 1 and \
               isinstance(response[0], basestring):

                response_data = response[0]

            else:

                for line in response:
                    response_buffer.write(line)

                if hasattr(response, 'close'):
                    response.close()

                response_data = response_buffer.getvalue()

            response_buffer.close()

            response_etag = sha1()
            response_etag.update(response_data)
            response_etag = '%s-%x' % (urlsafe_b64encode(response_etag.digest()).strip('='),
                                       len(response_data))

            headers = start_response_args['headers']

            if client_etag == response_etag:
                status = '304 Not Modified'
                headers = [item for item in headers if item[0] == 'Set-Cookie']
                response = ['']
            else:
                status = start_response_args['status']
                response = [response_data]

            headers.append(('Etag', response_etag))

            start_response(status, headers, start_response_args['exc_info'])

        else:
            response_etag = etag_info.get('response_etag', None)
            # If there is an Etag and is equal to the client one we are on a 304
            if response_etag and client_etag == response_etag:
                response = ['']

        return response class ErrorMiddleware(object):
    """
    Catch errors and report.
    """
    error_response = ['{"ok":false,"msg":"Request could not be processed!"}']
    error_headers = [('Content-Type', 'application/json; charset=utf-8'),
                     ('Content-Length', str(len(error_response[0])))]

    postonly_response = ['{"ok":false,"msg":"Post Only!"}']
    postonly_headers = [('Content-Type', 'application/json; charset=utf-8'),
                        ('Cache-Control', 'no-store'),
                        ('Content-Length', str(len(postonly_response[0]))),
                        ('Allow', 'POST')]

    def __init__(self, app, config):
        self.app = app
        self.config = config

    def __call__(self, environ, start_response):
        try:
            # To see exceptions thrown above this call (i.e. higher in the middleware stack
            # and exceptions in this file) see the devserver/devserver.log file
            return self.app(environ, start_response)
        except ApiUnavailable as e:
            json_data = _json_encoder.encode(e.value)
            msg = '{"ok":false,"msg":"Service Unavailable","data":%s}' % json_data
            headers = [('Content-Type', 'application/json; charset=utf-8'),
                       ('Content-Length', str(len(msg)))]
            start_response('503 Service Unavailable', headers)
            return [msg]
        except ApiNotImplemented:
            start_response('501 Not Implemented', self.error_headers)
            return self.error_headers
        except ApiException as e:
            json_msg_data = _json_encoder.encode(e.value)
            if e.json_data:
                msg = '{"ok":false,"msg":%s,"data":%s}' % (json_msg_data, _json_encoder.encode(e.json_data))
            else:
                msg = '{"ok":false,"msg":%s}' % json_msg_data
            headers = [('Content-Type', 'application/json; charset=utf-8'),
                       ('Content-Length', str(len(msg)))]
            start_response(e.status, headers)
            return [msg]
        except PostOnlyException:
            start_response('405 Method Not Allowed', self.postonly_headers)
            return self.postonly_response
        except:
            log_msg = 'Exception when processing request: %s' % environ['PATH_INFO']
            trace_string = format_exc()

            LOG.error(log_msg)
            LOG.error(trace_string)
            if asbool(self.config.get('debug')):
                print log_msg
                print trace_string

            start_response('500 Internal Server Error', self.error_headers)
            return self.error_response class ServiceStatus(object):

    # interval in seconds to poll our service status URL
    polling_interval = 4

    services_status = {}
    # requests are not queued up client-side if this is true
    # (should be true for any long polling interval to avoid
    # overloading our services)
    default_discard_requests = False

    @classmethod
    def set_ok(cls, service_name):
        cls.services_status[service_name] = {
            'status': 'ok',
            'running': True,
            'discardRequests': cls.default_discard_requests,
            'description': 'ok'
        }

    @classmethod
    def set_poll_interval(cls, value):
        cls.polling_interval = value

    @classmethod
    def get_poll_interval(cls):
        return cls.polling_interval

    @classmethod
    def set_status(cls, service_name, status):
        try:
            service_running = asbool(status['running'])
            cls.services_status[service_name] = {
                'status': 'ok' if service_running else 'unavailable',
                'running': service_running,
                'discardRequests': asbool(status.get('discardRequests', cls.default_discard_requests)),
                'description': status.get('description', 'ok' if service_running else 'unavailable')
            }
        except (KeyError, AttributeError):
            raise InvalidStatus()

    @classmethod
    def get_status(cls, service_name):
        try:
            return cls.services_status[service_name]
        except:
            raise ApiNotImplemented()

    @classmethod
    def get_status_list(cls):
        return cls.services_status

    @classmethod
    def check_status_decorator(cls, service_name):
        @decorator
        def wrapped_decorator(func, *args, **kwargs):
            service_status = cls.get_status(service_name)
            if service_status['running']:
                return func(*args, **kwargs)
            else:
                raise ApiUnavailable(service_status)

        return wrapped_decorator class GameSessionList(object):
    _instance = None    # Singleton instance
    _reload = False     # Flag to be set if the list should be reloaded

    def __init__(self):
        self.lock = Lock()
        self.lock.acquire()
        self._sessions = {}
        path = config.get('gamesessions.yaml', 'gamesessions.yaml')
        self.path = get_absolute_path(path)
        self.load_sessions()
        self.lock.release()


    @classmethod
    def get_instance(cls):
        """
        Return an instance of GameList.
        Effectively implement a singleton pattern
        """
        if cls._instance is None or cls._reload:
            cls._instance = GameSessionList()
            cls._reload = False
        return cls._instance


    # for debugging
    def list(self):
        arraylist = [ ]
        for s in self._sessions.values():
            arraylist.append(s.to_dict())
        return arraylist


    def purge_sessions(self):
        self.lock.acquire()
        self.load_sessions()

        purge_time = time() - (1 * 86400)  # 1 day
        delete_sessions = []
        for string_id in self._sessions:
            s = self._sessions[string_id]
            if s.created < purge_time:
                delete_sessions.append(string_id)

        for s in delete_sessions:
            del self._sessions[s]

        self.write_sessions()
        self.lock.release()


    def load_sessions(self):
        path = self.path
        self._sessions = {}

        if exists(path):
            f = open(path, 'r')
            try:
                gamesessions = yaml.load(f)
                if isinstance(gamesessions, dict):
                    for string_id in gamesessions:
                        file_gamesession = gamesessions[string_id]
                        try:
                            self._sessions[string_id] = GameSession.from_dict(file_gamesession)
                        except InvalidGameSession:
                            pass
                else:
                    LOG.error('Gamesessions file incorrectly formated')
            except (yaml.parser.ParserError, yaml.parser.ScannerError):
                pass
            finally:
                f.close()


    def write_sessions(self):
        f = open(self.path, 'w')
        file_sessions = {}
        ghost_sessions = set()
        for string_id in self._sessions:
            session = self._sessions[string_id]
            try:
                file_sessions[string_id] = session.to_dict()
            except InvalidGameSession:
                ghost_sessions.add(string_id)

        # remove any invalid sessions
        for g in ghost_sessions:
            del self._sessions[g]

        try:
            yaml.dump(file_sessions, f)
        finally:
            f.close()


    def create_session(self, user, game):
        if user is None or game is None:
            return None
        session = GameSession(game, user)
        with self.lock:
            self._sessions[session.gamesession_id] = session
            self.write_sessions()
            return session


    def remove_session(self, string_id):
        with self.lock:
            sessions = self._sessions
            if string_id in sessions:
                del sessions[string_id]
                self.write_sessions()
                return True
            else:
                return False

    def remove_game_sessions(self, user, game):
        username = user.username
        slug = game.slug

        with self.lock:
            sessions = self._sessions
            for s in sessions.values():
                if s.game.slug == slug and \
                   s.user.username == username:

                    del sessions[s.gamesession_id]

            self.write_sessions()


    def get_session(self, string_id):
        with self.lock:
            session = self._sessions.get(string_id, None)
            return session


    def update_session(self, session):
        with self.lock:
            self._sessions[session.gamesession_id] = session
            self.write_sessions()
            self.lock.release()
import logging
import os
import mimetypes
from beaker.middleware import CacheMiddleware, SessionMiddleware
from jinja2 import Environment, FileSystemLoader
from paste.registry import RegistryManager
from paste.deploy.converters import asbool, asint
from pylons import config
from pylons.wsgiapp import PylonsApp
from routes.middleware import RoutesMiddleware
from webob.util import status_reasons
from turbulenz_local.helpers import make_helpers
from turbulenz_local.routing import make_map
from turbulenz_local.middleware import MetricsMiddleware, LoggingMiddleware, GzipMiddleware, \
                                           StaticGameFilesMiddleware, StaticFilesMiddleware, \
                                           CompactMiddleware, EtagMiddleware, ErrorMiddleware
from turbulenz_local.lib.servicestatus import ServiceStatus
from turbulenz_local.models.gamesessionlist import GameSessionList
    root = os.path.dirname(os.path.abspath(__file__))
    paths = dict(root=root,
                 controllers=os.path.join(root, 'controllers'),
                 static_external_paths=[os.path.join(root, 'public', 'external')],
                 static_development_paths=[os.path.join(root, 'public', 'development')],
                 static_release_paths=[os.path.join(root, 'public', 'release')],
                 static_viewer_paths=[os.path.realpath(os.path.join(root, '..', '..'))],
                 templates=[os.path.join(root, 'templates')])

    # Initialize config with the basic options
    config.init_app(global_conf, app_conf, package='turbulenz_local', paths=paths)

    config['routes.map'] = make_map()
    config['pylons.app_globals'] = Globals()
    config['pylons.h'] = make_helpers(config)

    # Create the Jinja2 Environment
    config['pylons.app_globals'].jinja2_env = Environment(loader=FileSystemLoader(paths['templates']))

    # Jinja2's unable to request c's attributes without strict_c
    config['pylons.strict_c'] = True

    # CONFIGURATION OPTIONS HERE (note: all config options will override
    # any Pylons config options)
    config['pylons.response_options']['headers'] = {'Cache-Control': 'public, max-age=0',
                                                    'Pragma': 'no-cache'}


def __add_customisations():
    status_reasons[429] = 'Too Many Requests'


def __init_controllers():
    ServiceStatus.set_ok('userdata')
    ServiceStatus.set_ok('gameProfile')
    ServiceStatus.set_ok('leaderboards')
    ServiceStatus.set_ok('gameSessions')
    ServiceStatus.set_ok('badges')
    ServiceStatus.set_ok('profiles')
    ServiceStatus.set_ok('multiplayer')
    ServiceStatus.set_ok('customMetrics')
    ServiceStatus.set_ok('store')
    ServiceStatus.set_ok('datashare')
    ServiceStatus.set_ok('notifications')

    GameSessionList.get_instance().purge_sessions()


def make_app(global_conf, full_stack=True, static_files=True, **app_conf):
    """Create a Pylons WSGI application and return it

    ``global_conf``
        The inherited configuration for this application. Normally from
        the [DEFAULT] section of the Paste ini file.

    ``full_stack``
        Whether or not this application provides a full WSGI stack (by
        default, meaning it handles its own exceptions and errors).
        Disable full_stack when this application is "managed" by another
        WSGI middleware.

    ``static_files``
        Whether this application serves its own static files; disable
        when another web server is responsible for serving them.

    ``app_conf``
        The application's local configuration. Normally specified in the
        [app:<name>] section of the Paste ini file (where <name>
        defaults to main).
    """
    # Configure the Pylons environment
    load_environment(global_conf, app_conf)

    # Add missing mime types
    for k, v in app_conf.iteritems():
        if k.startswith('mimetype.'):
            mimetypes.add_type(v, k[8:])

    # The Pylons WSGI app
    app = PylonsApp()

    # Routing/Session/Cache Middleware
    app = RoutesMiddleware(app, config['routes.map'])
    if asbool(config.get('etag.enable', True)):
        app = EtagMiddleware(app, config)
    if asbool(config.get('compact.enable', True)):
        app = CompactMiddleware(app, config)
    app = SessionMiddleware(app, config)
    app = CacheMiddleware(app, config)

    # CUSTOM MIDDLEWARE HERE (filtered by error handling middlewares)
    if asbool(full_stack):
        app = ErrorMiddleware(app, config)

    # Establish the Registry for this application
    app = RegistryManager(app)

    if asbool(static_files):
        # Serve static files
        max_age = asint(config.get('cache_max_age.staticmax', 1))
        static_external_paths = config['pylons.paths']['static_external_paths']
        static_development_paths = config['pylons.paths']['static_development_paths']
        static_release_paths = config['pylons.paths']['static_release_paths']
        static_viewer_paths = config['pylons.paths']['static_viewer_paths']

        # Order is important for performance
        # file paths will be check sequentially the first time the file is requested
        if asbool(config.get('scripts.development', False)):
            all_path_items = [(path, 0) for path in static_development_paths]
        else:
            all_path_items = [(path, 28800) for path in static_development_paths]
        all_path_items.extend([(path, max_age) for path in static_external_paths])
        all_path_items.extend([(path, max_age) for path in static_release_paths])

        if asbool(config.get('viewer.development', 'false')):
            # We only need to supply the jslib files with the viewer in development mode
            all_path_items.extend([(path, 0) for path in static_viewer_paths])
            all_path_items.extend([(os.path.join(path, 'jslib'), 0) for path in static_viewer_paths])

        app = StaticFilesMiddleware(app, all_path_items)

        app = StaticGameFilesMiddleware(app, staticmax_max_age=max_age)