#
# MoinIIS v.3
#
# IIS ISAPI extension to start MoinMoin from under IIS effectively
#
# Set the virtual path to your wiki instanse
#
virtualPath = '/mywiki'

import sys
#
# If MoinMoin is not set to the default package path, 
# uncomment and edit this:
#
#sys.path.append('C:/MoinWiki-1.3.5/Lib/site-packages')
sys.path.append('C:/Moin/Lib/site-packages')

from StringIO import StringIO
from MoinMoin import config, wikiutil
from MoinMoin.request import RequestBase
from isapi import InternalReloadException
from isapi import isapicon
from isapi.simple import SimpleExtension
import MoinMoin
import cgi, urllib, os, stat, threading
import win32event, win32file, winerror, win32con, threading
from MoinMoin.auth import http

def debug(log):
    f = open('/tmp/env.log', 'a+')
    try:
        f.write(log + '\n')
    finally:
        f.close()

if hasattr(sys, "isapidllhandle"):
    import win32traceutil

try:
    reload_counter += 1
    reload( wikiconfig )
    reload( MoinMoin.multiconfig )
    reload( MoinMoin )
    print 'Modules reloaded'
except NameError:
    reload_counter = 0
    import wikiconfig
        
class ReloadWatcherThread(threading.Thread):
    def __init__(self):
        self.filename = __file__
        self.change_detected = False
        if self.filename[:4] == '\\\\?\\':
            self.filename = self.filename[4:];
        if self.filename.endswith("c") or self.filename.endswith("o"):
            self.filename = self.filename[:-1]
        self.root_dir = os.path.dirname(self.filename)
        self.handle = win32file.FindFirstChangeNotification(
                        self.root_dir,
                        False, # watch tree?
                        win32con.FILE_NOTIFY_CHANGE_LAST_WRITE)
        threading.Thread.__init__(self)

    def run(self):
        last_time = os.stat(self.filename)[stat.ST_MTIME]
        while 1:
            try:
                win32event.WaitForSingleObject(self.handle, 
                                                    win32event.INFINITE)
                win32file.FindNextChangeNotification(self.handle)
            except win32event.error, details:
                # handle closed - thread should terminate.
                if details[0] != winerror.ERROR_INVALID_HANDLE:
                    raise
                break
            this_time = os.stat(self.filename)[stat.ST_MTIME]
#            print "Detected file change - flagging for reload."
            self.change_detected = True
#===============================================================================
#            if this_time != last_time:
#                last_time = this_time
#===============================================================================
    
    def stop(self):
        win32file.FindCloseChangeNotification(self.handle)

# The moinmoin start extention

class Extension(SimpleExtension):
    ''' The MoinMoin Wiki IIS ISAPI starter
    '''
    def __init__(self):
        self.reload_watcher = ReloadWatcherThread()
        self.reload_watcher.start()

    def HttpExtensionProc(self, ecb):
        if self.reload_watcher.change_detected:
            raise InternalReloadException
        
#        print "serving request, changind dir to ",self.reload_watcher.root_dir
        os.chdir(self.reload_watcher.root_dir)
        try:
            r = RequestISAPI(ecb)
            r.run()
        except:
            einfo = sys.exc_info()
            print >>ecb, "Content-type: text/html\r\n\r\n<html><head></head><body>"
            print >>ecb, "<h1>Exception</h1>: "
            import traceback
            print >>ecb, '<p><pre>',traceback.format_exc(),'</pre>'
            print traceback.format_exc()
            print >>ecb, '</body></html>'

        ecb.close()
        return isapicon.HSE_STATUS_SUCCESS
    
    def TerminateExtension(self, status):
        self.reload_watcher.stop()

# The entry points for the ISAPI extension.
def __ExtensionFactory__():
    return Extension()

class EnvAdaptor(dict):
    """Adaptor from ecb to env."""
    
    def __init__(self, ecb, force_env={}):
        self.ecb = ecb
        self.force_env = force_env
    
    def get(self, key, default=None):
        try:
            return self.force_env[key]
        except KeyError:
            try:
                return self[key]
            except KeyError:
                val = self.ecb.GetServerVariable(key, default)
                if val:
                    #debug('add key %s' % key)
                    self[key] = val
                return val
    
    def __contains__(self, key):
        #debug('%s in env?' % key)
        haskey = self.force_env.has_key(key) or self.has_key(key)
        if haskey:
            #debug('haskey')
            return True
        else:
            val = self.ecb.GetServerVariable(key, None)
            if val:
                self[key] = val
                #debug('getkey')
                return True
            else:
                #debug('nokey')
                return False

class RequestISAPI( RequestBase ):
    
    def __init__(self, ecb=None):
        """ Sets the common Request members by parsing an ISAPI extension
            environment
        """
        self.buffer = []
        self.ecb = ecb
        env = self.env = EnvAdaptor(self.ecb)
        self.response = None
        sname = virtualPath
        
        # different from _setup_vars_from_std_env's implement
        env.force_env['SCRIPT_NAME'] = sname
        # Make sure http referer use only ascii (IE again)
        env.force_env['HTTP_REFERER'] = unicode(
            env.get('HTTP_REFERER', ''), 'ascii', 'replace').encode(
                'ascii', 'replace')
        # not the same as _setup_vars_from_std_env believe
        env.force_env['PATH_INFO'] = wikiutil.decodeWindowsPath(
            ecb.PathInfo.replace(sname, '', 1)).encode("utf-8")
        # cgi.FieldStorage need this var, or will treat the environment as CGI.
        env['QUERY_STRING'] = env.get('QUERY_STRING', '')
        # _setup_vars_from_std_env doesn't deal with it
        ac = env.get('HTTP_ACCEPT_CHARSET', '')
        self.setAcceptedCharsets(ac)
        
        self._setup_vars_from_std_env(self.env)

        RequestBase.__init__(self)

        self.ret_code = None;
        
    def open_logs(self):
        # create log file for catching stderr output
        if not self.opened_logs: #IGNORE:E0203
            self.opened_logs = 1
            sys.stderr = open(os.path.join(self.cfg.data_dir, 'error.log'), 'at')
   
    def setup_args(self, form=None):
        sio = StringIO()
        ecb = self.ecb
        sio.write(ecb.AvailableData)
        rest = ecb.TotalBytes - sio.len
        if rest > 0:
            sio.write( ecb.ReadClient( rest ) )
        sio.seek(0)
        if self.ecb.Method.upper() == "POST":
            h = {'content-type': self.ecb.ContentType }
            form = cgi.FieldStorage(fp=sio, environ=self.env, headers=h)
        else:
            form = cgi.FieldStorage(fp=sio, environ=self.env)
        return self._setup_args_from_cgi_form(form)
    
    def read(self, n=None):
        print "Ignoring read", n
        
    def write(self, *data):
        """ Write to output stream.
        """
        self.buffer.extend(data)

    def flush(self):
        pass
    
    def setResponseCode(self, code, message=None):
        if not code:
            raise 'ERROR: response code is None!'
        self.response = str(code)
        if message != None:
            self.response += ' ' + message;
    
    def finish(self):
        RequestBase.finish(self)
        headers = '\r\n'.join(self.all_headers)
        response = self.getHeader('Status')
        if response:
            self.response = response       
        if not self.response: self.response = '200 Ok'
        self.ecb.SendResponseHeaders( self.response, headers )
        if headers.find('text/html') > 0:
            self.ecb.write( 
                '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd><html>' )
        self.ecb.write('\r\n' * 2)
        self.ecb.write(self.encode(self.buffer))
        
    def getHeader(self,name):
        name = name.lower()+': '
        for x in self.all_headers:
            if x.lower().startswith(name):
                return x[len(name):]
        return None
        
    # Accessors --------------------------------------------------------
    
    def getPathinfo(self):
        """ Return the remaining part of the URL. """
        pathinfo = self.path_info

        # Fix for bug in IIS/4.0
        if os.name == 'nt':
            scriptname = self.getScriptname()
            if pathinfo.startswith(scriptname):
                pathinfo = pathinfo[len(scriptname):]

        return pathinfo

    # Headers ----------------------------------------------------------
    
    def setHttpHeader(self, header):
        self.user_headers.append(header)

    def http_headers(self, more_headers=[]):
        # Send only once
        if getattr(self, 'sent_headers', None):
            return
        
        self.sent_headers = 1
        
        have_ct = 0
        self.all_headers = []
        # send http headers
        for header in more_headers + getattr(self, 'user_headers', []):
            if header.lower().startswith("content-type:"):
                # don't send content-type multiple times!
                if have_ct: continue
                have_ct = 1
            if type(header) is unicode:
                header = header.encode('ascii')
            self.all_headers.append(header)

        if not have_ct:
            self.all_headers.append("Content-type: text/html;charset=%s" % config.charset)
        #from pprint import pformat
        #sys.stderr.write(pformat(more_headers))
        #sys.stderr.write(pformat(self.user_headers))

# Post install hook for our entire script
def PostInstall(params, options):
    print
    print "The MoinMoin ISAPI is installed."
    print "Point your browser to ",virtualPath,"to check it"

if __name__=='__main__':
    # If run from the command-line, install ourselves.
    from isapi.install import *
    params = ISAPIParameters(PostInstall = PostInstall)
    # Setup the virtual directories - this is a list of directories our
    # extension uses - in this case only 1.
    # Each extension has a "script map" - this is the mapping of ISAPI
    # extensions.
    sm = [
        ScriptMapParams(Extension="*", Flags=0)
    ]
    vd = VirtualDirParameters(Name=virtualPath,
                              Description = Extension.__doc__,
                              ScriptMaps = sm,
                              ScriptMapUpdate = "replace"
                              )
    params.VirtualDirs = [vd]
    # Setup our custom option parser.
    from optparse import OptionParser
    parser = OptionParser('') # black usage, so isapi sets it.
    HandleCommandLine(params, opt_parser=parser)
        
