##############################################################################
#
# Copyright (c) 2005 Chris McDonough. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
##############################################################################

""" Zope clock server.  Generate a faux HTTP request on a regular basis
by coopting the asyncore API. """

import sys, string, os, socket, time, StringIO, base64
import asyncore
from ZServer.medusa.thread.select_trigger import trigger
from ZServer.medusa.http_server import http_request
from ZServer.medusa.default_handler import unquote
from timeslice import timeslice
from ZServer.PubCore import handle
from ZServer.HTTPResponse import make_response
from ZPublisher.HTTPRequest import HTTPRequest

_last_slices = {} # one entry will be in this map per clock server

class LogHelper:
    def __init__(self, logger):
        self.logger = logger

    def log(self, ip, msg, **kw):
        self.logger.log(ip + ' ' + msg)

class DummyChannel:
    # we need this minimal do-almost-nothing channel class to appease medusa
    addr = ['127.0.0.1']
    closed = 1

    def __init__(self, server):
        self.server = server
        
    def push_with_producer(self):
        pass

    def close_when_done(self):
        pass
    
class ClockServer(asyncore.dispatcher):
    SERVER_IDENT = 'Zope Clock' # required by ZServer

    def __init__ (self, method, period=60, user=None, password=None,
                  host=None, logger=None):
        self.myid = id(self)
        _last_slices[self.myid] = timeslice(period)
        self.period = period
        self.method = method

        h = self.headers = []
        h.append('User-Agent: Zope Clock Server Client')
        h.append('Accept: text/html,text/plain')
        if not host:
            host = socket.gethostname()
        h.append('Host: %s' % host)
        auth = False
        if user and password:
            encoded = base64.encodestring('%s:%s' % (user, password))
            encoded = encoded.replace('\012', '')
            h.append('Authorization: Basic %s' % encoded)
            auth = True

        asyncore.dispatcher.__init__(self)
        self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
        self.logger = LogHelper(logger)
        self.log_info('Clock server for "%s" started (user: %s, period: %s)'
                      % (method, auth and user or 'Anonymous', self.period))

    def get_requests_and_response(self):
        out = StringIO.StringIO()
        s_req = '%s %s HTTP/%s' % ('GET', self.method, '1.0')
        req = http_request(DummyChannel(self), s_req, 'GET', self.method,
                           '1.0', self.headers)
        env = self.get_env(req)
        resp = make_response(req, env)
        zreq = HTTPRequest(out, env, resp)
        return req, zreq, resp

    def get_env(self, req):
        env = {}
        (path, params, query, fragment) = req.split_uri()
        if params:
            path = path + params # undo medusa bug
        while path and path[0] == '/':
            path = path[1:]
        if '%' in path:
            path = unquote(path)
        if query:
            # ZPublisher doesn't want the leading '?'
            query = query[1:]
        env['REQUEST_METHOD']='GET'
        env['SERVER_PORT']='Clock'
        env['SERVER_NAME']='Zope Clock Server'
        env['SERVER_SOFTWARE']='Zope'
        env['SERVER_PROTOCOL']='HTTP/1.0'
        env['channel.creation_time']=time.time()
        env['SCRIPT_NAME']=''
        env['PATH_INFO']= '/' + path
        env['PATH_TRANSLATED']= os.path.normpath(
            os.path.join(os.getcwd(), env['PATH_INFO']))
        if query:
            env['QUERY_STRING'] = query
        env['GATEWAY_INTERFACE']='CGI/1.1'
        env['REMOTE_ADDR']='0'
        for header in req.header:
            key,value=header.split(":",1)
            key=key.upper()
            value=value.strip()
            key='HTTP_%s' % ("_".join(key.split( "-")))
            if value:
                env[key]=value
        return env

    def readable(self):
        # generate a request at most once every self.period seconds
        slice = timeslice(self.period)
        if slice != _last_slices.get(self.myid):
            # no need for threadsafety here, as we're only ever in one thread
            _last_slices[self.myid] = slice
            req, zreq, resp = self.get_requests_and_response()
            handle('Zope', zreq, resp)
        return 0

    def handle_read(self):
        pass

    def handle_write (self):
        self.log_info ('unexpected write event', 'warning')

    def writable(self):
        return 0

    def handle_error (self):      # don't close the socket on error
        (file,fun,line), t, v, tbinfo = asyncore.compact_traceback()
        self.log_info('Problem in Clock (%s:%s %s)' % (t, v, tbinfo),
                      'error')



