#
# MoinIIS v.3
#
# IIS ISAPI extension to start MoinMoin from under IIS effectively
#
# Set the virtual path to your wiki instanse
#
virtualPath = '/themoin'

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')

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

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 RequestISAPI( RequestBase ):
    def __init__(self,ecb=None):
        """ Sets the common Request members by parsing an ISAPI extension
            environment
        """
        self.ecb = ecb
        sname = virtualPath
        self.script_name = sname
        self.env = {}
        self.response = None
        
        self.buffer = []
        self.http_accept_language = ecb.GetServerVariable('HTTP_ACCEPT_LANGUAGE', 'en')
        self.server_name = ecb.GetServerVariable('SERVER_NAME', 'localhost')
        self.server_port = ecb.GetServerVariable('SERVER_PORT', '80')
        self.http_host = ecb.GetServerVariable('HTTP_HOST','localhost')
        # Make sure http referer use only ascii (IE again)
        self.http_referer = unicode(ecb.GetServerVariable('HTTP_REFERER', ''), 'ascii',
                                    'replace').encode('ascii', 'replace')
        self.saved_cookie = ecb.GetServerVariable('HTTP_COOKIE', '')

        path_info = wikiutil.decodeWindowsPath(ecb.PathInfo.replace(sname,'',1)).encode("utf-8")
        self.path_info = self.decodePagename(path_info)
        self.env['PATH_INFO'] = path_info + '/' + sname;
        
        self.env['QUERY_STRING'] = self.query_string = ecb.QueryString
        server_software = ecb.GetServerVariable('SERVER_SOFTWARE', '')

        self.env['REQUEST_METHOD'] = self.request_method = ecb.Method

        self.remote_addr = ecb.GetServerVariable('REMOTE_ADDR', '')
        self.http_user_agent = ecb.GetServerVariable('HTTP_USER_AGENT', '')
        self.is_ssl = ecb.GetServerVariable('SSL_PROTOCOL', '') != '' \
            or ecb.GetServerVariable('SSL_PROTOCOL_VERSION', '') != '' \
            or ecb.GetServerVariable('HTTPS', 'off') == 'on'

        if self.server_port.strip() and self.server_port != '80':
            port = ':' + str(self.server_port)
        else:
            port = ''

        self.url = urllib.quote((self.server_name + port + 
            self.path_info).encode('utf8'))+'?' + self.query_string
        self.request_uri = '';
        
        ac = ecb.GetServerVariable('HTTP_ACCEPT_CHARSET', '')
        self.accepted_charsets = self.parse_accept_charset(ac)
        
        self.auth_username = None

        # need config here, so check:
        self._load_multi_cfg()
        MoinMoin.multiconfig.getConfig(self.url)

        if self.cfg.auth_http_enabled:
            auth_type = ecb.GetServerVariable('AUTH_TYPE','')
            if auth_type in ['Basic', 'Digest', 'NTLM', ]:
                username = ecb.GetServerVariable('REMOTE_USER','')
                if auth_type == 'NTLM':
                    # converting to standard case so that the user can even enter wrong case
                    # (added since windows does not distinguish between e.g. "Mike" and "mike")
                    username = username.split('\\')[-1] # split off domain e.g. from DOMAIN\user
                    # this "normalizes" the login name from {meier, Meier, MEIER} to Meier
                    # put a comment sign in front of next line if you don't want that:
                    username = username.title()
        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 )
        res = '\r\n'*2;
        for s in self.buffer:
            if s:
                res += s
        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>' )
        try:
            self.ecb.write(res.encode('utf8'))
        except:
            self.ecb.write(res)
#        else:
#            self.ecb.write( res ) 
        
    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)
        
