#!/usr/bin/env python
########################################################################
#
# PDFfit2           by DANSE Diffraction group
#                   Simon J. L. Billinge
#                   (c) 2006 trustees of the Michigan State University.
#                   All rights reserved.
#
# File coded by:    Jiwu Liu
#
# See AUTHORS.txt for a list of people who contributed.
# See LICENSE.txt for license information.
#
########################################################################


import os
import sys
import socket
from SimpleXMLRPCServer import SimpleXMLRPCServer as BaseServer

from diffpy.pdffit2.PdfFit import PdfFit

def getFirstOpenPort():
    """Get the first open local port, starting at 8000.

    return: the first open port number or -1 if an open port cannot be found.
    """
    ip = "0.0.0.0"
    portnum = 8000
    maxport = 65535
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    while not s.connect_ex((ip,portnum)) and portnum <= maxport:
        portnum += 1
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.close()
    if portnum == maxport:
        return -1
    else:
        return portnum

class Server(BaseServer):
    """A basic server class for serving PDFFit over internet"""
    # interval in seconds
    Timeout = 5
    def __init__(self, host, port):
        """initialize

        host -- hostname/ipaddress that the server binds to
        port -- port the server is listening on
        """
        self.running = True
        BaseServer.__init__(self, (host, port), logRequests=0)
        self.idle = 0
        return

    def server_bind(self):
        """overloaded function to set timeout
        """
        self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.socket.settimeout(Server.Timeout)
        BaseServer.server_bind(self)
        return

    def isRunning(self):
        """Test if the server is running by client

        return: True for running, otherwise False
        """
        return self.running

    def serve(self):
        """server request
        """
        while self.running:
            self.handle_request()
        return

    def get_request(self):
        """overloaded function in order to catch timeout error
        """
        try:
            req = self.socket.accept()
            # successfully accepted request
            self.idle = 0
            return req
        except socket.timeout:
            self.idle += Server.Timeout
            if self.idle > 500000: # idle for approximately 6 days
                self.running = False # then don't wait any longer
            raise
        except socket.error:
            raise

    def kill_server(self):
        """kill by client"""
        self.running = False
        return 1

class NicePdfFit(PdfFit):
    """Wrapped PdfFit class whose methods do not return None."""
    def __init__(self):
        PdfFit.__init__(self)
        self.__wrapMethods()
        return

    def __wrapMethods(self):
        """Wrap the methods of PdFFit so that they return "None" not None."""
        def _funcBuilder(itemName):
            obj = getattr(self, itemName)

            # not a function, do nothing
            if not hasattr(obj, 'func_name'):
              return obj
            def _f(*args, **kwargs):
                retval = obj(*args, **kwargs)
                if retval is None:
                    return "None"
                else:
                    return retval
            return _f

        itemNames = [item for item in dir(self) if item[0] != '_' ]
        for name in itemNames:
            setattr(self, name, _funcBuilder(name))
        return "None"

# End class NicePdfFit

def main():
    # Scan for an open port before starting the server.
    host = '0.0.0.0'
    port = getFirstOpenPort()
    if port == -1:
        sys.stderr.write("No open port available")
        sys.exit(1)

    # Print the port number so that the process that started the server
    # can get the port number.
    print port

    # make self a daemon process
    try:
        pid = os.fork()
        if pid > 0:
            sys.exit(0) # decouple from parent
    except OSError,e:
        sys.stderr.write( "Unable to fork" )
        sys.exit(2)

    # close up input output
    sys.stdout.flush()
    sys.stderr.flush()
    fin  = file('/dev/null', 'r')
    fout = file('/dev/null', 'a+')
    ferr = file('/dev/null', 'a+', 0)
    os.dup2(fin.fileno(), sys.stdin.fileno())
    os.dup2(fout.fileno(), sys.stdout.fileno())
    os.dup2(ferr.fileno(), sys.stderr.fileno())

    # start server
    server = Server(host, port)
    server.register_function( server.kill_server )
    server.register_function( server.isRunning )
    server.register_instance( NicePdfFit() )
    server.register_introspection_functions()
    server.serve()

    return

# main
if __name__ == '__main__':
    # invoke the application shell
    main()

# End of file
