# -*- Mode: Python; tab-width: 4 -*-

# This is a simple python server-side script handler.

# A note about performance: This is really only suited for 'fast'
# scripts: The script should generate its output quickly, since the
# whole web server will stall otherwise.  This doesn't mean you have
# to write 'fast code' or anything, it simply means that you shouldn't
# call any long-running code, [like say something that opens up an
# internet connection, or a database query that will hold up the
# server].  If you need this sort of feature, you can support it using
# the asynchronous I/O 'api' that the rest of medusa is built on.  [or
# you could probably use threads]

# Put your script into your web docs directory (like a cgi-bin
# script), make sure it has the correct extension [see the overridable
# script_handler.extension member below].
# 
# There's lots of things that can be done to tweak the restricted
# execution model.  Also, of course you could just use 'execfile'
# instead (this is now the default, see class variable
# script_handler.restricted)

import rexec
import re
import string
import StringIO
import sys

import counter
import default_handler
import producers

split_path = default_handler.split_path
unquote    = default_handler.unquote

class script_handler:

    extension = 'mpy'
    restricted = 0
    
    script_regex = re.compile (
            r'.*/([^/]+\.%s)' % extension,
            re.IGNORECASE
            )
    
    def __init__ (self, filesystem):
        self.filesystem = filesystem
        self.hits = counter.counter()
        self.exceptions = counter.counter()
        
    def match (self, request):
        [path, params, query, fragment] = request.split_uri()
        m = self.script_regex.match (path)
        return (m and (m.end() == len(path)))
        
    def handle_request (self, request):
    
        [path, params, query, fragment] = split_path (request.uri)
        
        while path and path[0] == '/':
            path = path[1:]
            
        if '%' in path:
            path = unquote (path)
            
        if not self.filesystem.isfile (path):
            request.error (404)
            return
        else:
        
            self.hits.increment()
            
            request.script_filename = self.filesystem.translate (path)
            
            if request.command in ('put', 'post'):
                    # look for a Content-Length header.
                cl = request.get_header ('content-length')
                length = int(cl)
                if not cl:
                    request.error (411)
                else:
                    collector (self, length, request)
            else:
                self.continue_request (
                        request,
                        StringIO.StringIO() # empty stdin
                        )
                
    def continue_request (self, request, stdin):
        temp_files = stdin, StringIO.StringIO(), StringIO.StringIO()
        old_files = sys.stdin, sys.stdout, sys.stderr
        
        if self.restricted:
            r = rexec.RExec()
            
        try:
            sys.request = request
            sys.stdin, sys.stdout, sys.stderr = temp_files
            try:
                if self.restricted:
                    r.s_execfile (request.script_filename)
                else:
                    execfile (request.script_filename)
                request.reply_code = 200
            except:
                request.reply_code = 500
                self.exceptions.increment()
        finally:
            sys.stdin, sys.stdout, sys.stderr = old_files
            del sys.request
            
        i,o,e = temp_files
        
        if request.reply_code != 200:
            s = e.getvalue()
        else:
            s = o.getvalue()
            
        request['Content-Length'] = len(s)
        request.push (s)
        request.done()
        
    def status (self):
        return producer.simple_producer (
                '<li>Server-Side Script Handler'
                + '<ul>'
                + '  <li><b>Hits:</b> %s' % self.hits
                + '  <li><b>Exceptions:</b> %s' % self.exceptions
                + '</ul>'
                )
        
        
class persistent_script_handler:

    def __init__ (self):
        self.modules = {}
        self.hits = counter.counter()
        self.exceptions = counter.counter()
        
    def add_module (self, name, module):
        self.modules[name] = module
        
    def del_module (self, name):
        del self.modules[name]
        
    def match (self, request):
        [path, params, query, fragment] = request.split_uri()
        parts = string.split (path, '/')
        if (len(parts)>1) and self.modules.has_key (parts[1]):
            module = self.modules[parts[1]]
            request.module = module
            return 1
        else:
            return 0
            
    def handle_request (self, request):
        if request.command in ('put', 'post'):
                # look for a Content-Length header.
            cl = request.get_header ('content-length')
            length = int(cl)
            if not cl:
                request.error (411)
            else:
                collector (self, length, request)
        else:
            self.continue_request (request, StringIO.StringIO())
            
    def continue_request (self, request, input_data):
        temp_files = input_data, StringIO.StringIO(), StringIO.StringIO()
        old_files = sys.stdin, sys.stdout, sys.stderr
        
        try:
            sys.stdin, sys.stdout, sys.stderr = temp_files
            # provide a default
            request['Content-Type'] = 'text/html'
            try:
                request.module.main (request)
                request.reply_code = 200
            except:
                request.reply_code = 500
                self.exceptions.increment()
        finally:
            sys.stdin, sys.stdout, sys.stderr = old_files
            
        i,o,e = temp_files
        
        if request.reply_code != 200:
            s = e.getvalue()
        else:
            s = o.getvalue()
            
        request['Content-Length'] = len(s)
        request.push (s)
        request.done()
        
class collector:

    def __init__ (self, handler, length, request):
        self.handler = handler
        self.request = request
        self.request.collector = self
        self.request.channel.set_terminator (length)
        self.buffer = StringIO.StringIO()
        
    def collect_incoming_data (self, data):
        self.buffer.write (data)
        
    def found_terminator (self):
        self.buffer.seek(0)
        self.request.collector = None
        self.request.channel.set_terminator ('\r\n\r\n')
        self.handler.continue_request (
                self.request,
                self.buffer
                )
