#!/usr/bin/python

""" Client to CDSXmatch Catalog server  (designed by FX Pinneau)
    G.Landais (CDS) 08/02/2012
    
    Options:
    --help: help
    --verbose: verbose
    --mirror: alias of --dir=mirror
    --port: port number
    --host: hostname (default localhost)
    --test: test a catalog (ex: -test -source wise_prelim)
     --ack: test server availability
    --ipix: query using HEALPix number (--ipix order:ipix)
  --format: tsv|csv
--noformat: no formatting in output
     --dir: home where are located the data (if they exist)
            (use --dir=mirror to use the var Env. Vroot (for CDS mirror install))
            if file is not found, the program use --host=${COCAT}
--noheader: remove meta and header from output
     --box: box query (mode zone) (ex: --box=ram,decm,raM,decM)
 
    (ASU) arguments:
      -source: the name survey (ex: WISE_PRELIM)
       -recno: query a record
               get an offset: -recno 10..100 get the 100 next records after 10
 -print_recno: print the recno
           -c: the position
         -c.r: the radius (cone) 
              (alternative: -c.rs=rad (in sec), -c.rm=rad (min) -c.rd=rad (deg))
         -c.u: the unity sec|min|deg 
     -out.max: the limit of records returned
       -whole: the entire catalog

    Other arguments:
         -id colname=value
         -col: columns (splited by ',')
      -filter: constraints on column
               ex: -filter "W2mag<5"
                   -filter "W2mag-W1mag<0.1"
           -l: constraints on column (alternative to -filter)
               ex: -lW2mag 5, (i.e. W2mag>5)
                   -lW2mag 5,10 (i.e 5<W2mag<10)
                   -lW2mag-W1mag ,0.1 (i.e. W2mag-W1mag<0.1)
                    
     
     Ex:
     ./catClient.py -source WISE_PRELIM  -c.rm 3 -out.max 10 -c 45.0+59.3 

"""

import sys, signal
import traceback
import socket
import time
import os
import re
import errno
import ssl

if int(sys.version[0]) < 3:
    import urllib as ulib
else:
    import urllib.parse as ulib

DEFAULT_SERVER = "axel.u-strasbg.fr"
if "DEFAULT_COCAT" in os.environ:
    DEFAULT_SERVER = os.environ["DEFAULT_COCAT"]

DEFAULT_PORT = 1800
DEFAULT_PORT_MONITOR = 1810
DEFAULT_TIMEOUT = 300
DEFAULT_TIMEOUT_CONNECTION = 10
MIN_FREE_CONNECTIONS = 5

DEFAULT_USER_AGENT = "python-cdspack"
DEFAULT_FORMAT = "tsv"
DEFAULT_RADIUS = 2
DEFAULT_RADIUS_UNIT = "min"

QUERYCAT_URL = "/viz-bin/catClient.cgi"
SOCKET_SIZE = 1024

def handler(signum, frame):
    raise IOError("(Error) Timeout")

# ------------------------------------------------------------------------------
class XmatchCatError(Exception):
    """Discution error with the CDSXmatch catalog server"""
    
    def __init__(self, message):
        self.msg = "(XmatchCatError) %s" % message

    def __str__(self):
        return self.msg


# ------------------------------------------------------------------------------
class XmatchCatCom:
    """ Communication with  the server """

    def __init__(self, port=DEFAULT_PORT, host='localhost', noheader=False):
        """Constructor
        :param port: port (default DEFAULT_PORT)
        :param host: host server (default local)
        :param noheader: output format without headers
        """
        self.port = port
        self.host = host
        self.sock = None
        self.noheader = noheader
        self.__max_line_iteration = 30

    def connect(self, timeout=DEFAULT_TIMEOUT):
        """connect to the socket
        :param timeout: timeout for connection (default DEFAULT_TIMEOUT)
        """
        try:
            self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            if timeout: 
                self.sock.settimeout(DEFAULT_TIMEOUT_CONNECTION) # timeout connection
                signal.signal(signal.SIGALRM, handler)
                signal.alarm(timeout)
            self.sock.connect((self.host, int(self.port)))
        except socket.error as msg:
            raise XmatchCatError(msg)

    def last_line(self, s):
        """usefull function to get the previous char read after '\n'.
           socket doesn't read line by line. The function read string until the first '\n'
        :return: the last line
        """
        i = len(s)-1
        for n in range(self.__max_line_iteration):
            if i<0 : break
            if s[i] == '\n': 
                return s[i+1:]
            i -= 1
        return s[i:]

    def send(self, asu=None, message=None):
        """send query
        :param asu: ASUparameters
        :param message: string to send to server
        """
        pass

    def get(self, out=sys.stdout):
        """get data and print result.
        :param out: outpustream
        """
        pass

    def ack(self):
        """test server availability
        :return: number of free connection
        """
        try:
            self.send(message="ACK")
            line = self.sock.recv(SOCKET_SIZE).decode()
            i = int(line)
            return i
        except Exception as err:
            raise XmatchCatError(err)

    def close(self):
        """close socket"""
        self.sock.close()

# ------------------------------------------------------------------------------
class XmatchCatComHTTP(XmatchCatCom):
    """ Communication with the server using HTTP connection """
    
    def __init__(self, port=80, host='localhost', noheader=False):
        """Constructor
        :pram port: default 80(443 HTTPS possible)
        :param host:server (default local)
        :param noheader: no header in output
        """
        XmatchCatCom.__init__(self, port, host=host, noheader=noheader)

    def send(self, asu=None, message=None):
        if asu is None: 
            raise Exception("asu parameter required for HTTP query")

        try:
            h = {}
            exp = re.compile("^--.*")
            for m in asu.param:
                if m.find("--test")==0: 
                    if asu.param[m] : h[m] = asu.param[m]
                if exp.search(m): continue
                if asu.param[m] != False and asu.param[m] and asu.param[m]!="*":
                    h[m] = asu.param[m]
            params = ulib.urlencode(h)

            protocole = "http"
            if self.port != 80:
                protocole = "https"
                self.sock = ssl.wrap_socket(self.sock,
                                            keyfile=None, certfile=None, server_side=False,
                                            cert_reqs=ssl.CERT_NONE, ssl_version=ssl.PROTOCOL_SSLv23)
            if asu.param["--verbose"]:
                sys.stderr.write("----catClient: GET {0}://{1}{2}?{3}\n".format(protocole, self.host, QUERYCAT_URL, params))
            self.sock.send(("GET {0}://{1}{2}?{3} HTTP/1.0\r\n".format(protocole, self.host, QUERYCAT_URL, params)).encode())

            useragent = DEFAULT_USER_AGENT
            if os.getenv("CATCLIENT_AGENT"): useragent = os.getenv("CATCLIENT_AGENT")

            self.sock.send(("User-Agent:"+useragent+"\r\n").encode())
            self.sock.send(("Host: "+self.host+"\r\n\r\n").encode())

        except socket.error as msg:
            raise XmatchCatError(msg)

    def get(self, out=sys.stdout):
            expTail = re.compile(".*(#[ -]*\d+ *matches).*")

            try : 
                # skip header
                while True:
                    line = self.sock.recv(SOCKET_SIZE).decode()
                    if not line: break

                    n = line.find("#+++ ERROR")
                    if n > -1:
                         raise XmatchCatError(line[n:]);
                    n = line.find("# Catalog:")
                    if n>-1:
                        if self.noheader is False:
                            out.write("# catClient server {0}:{1}\n".format(self.host, self.port))
                        out.write(line[n:])
                        break

                # read data
                buf = ""
                while True:
                    line = self.sock.recv(SOCKET_SIZE).decode()
                    if not line: break

                    complete_line = buf+line
                    n = complete_line.find("#-")
                    if n > -1:
                        mo = expTail.search(complete_line[n:])
                        if mo:
                            if n<len(buf):
                                # comment already written - stop to the first CR
                                out.write(line[:line.find('\n')])
                            else:
                                out.write(line[:line.find("#-")])
                                out.write(mo.group(1))
                            break

                    elif complete_line.find("#+++ ERROR") > -1:
                        if complete_line.find("No such file or directory ") > 0:
                            raise XmatchCatError("No such file or directory")
                        else:
                            raise XmatchCatError(line)

                    out.write(line)
                    buf = self.last_line(line)

            except OSError as e:
                # do not raise broken pipe error !
                if e.errno != errno.EPIPE: 
                    raise XmatchCatError(e);
            except Exception as msg:
                raise XmatchCatError(msg);
 
# ------------------------------------------------------------------------------
class XmatchCatComSock(XmatchCatCom):
    """ Communication with the server using socket connection """
    
    def __init__(self, port=DEFAULT_PORT, host='localhost', noheader=False):
        """Constructor
        :param port: sock port (defaut DEFAULT_PORT)
        :param host: server (default local)
        :param noheader: no header in output
        """
        XmatchCatCom.__init__(self, port, host=host, noheader=noheader)
        self.iter = None
        self.__is_connected = False

    def connect(self, timeout=DEFAULT_TIMEOUT):
        try:
            self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            if timeout: 
                self.sock.settimeout(DEFAULT_TIMEOUT_CONNECTION) # timeout connection
                signal.signal(signal.SIGALRM, handler)
                signal.alarm(timeout)
            self.sock.connect((self.host, self.port))
            self.__is_connected = True

        except socket.error as msg:
            self.__is_connected = False
            raise XmatchCatError(msg)
    

    def send(self, asu=None, message=None):
        if asu is None and message is None:
            raise Exception("send requires argument")

        if asu :
            message = asu.toxmatchparam()

            if asu.param["--verbose"]:
                sys.stderr.write("----catClient:sock:"+message+"\n")
        try:
            self.sock.send((message+" START ").encode())
        except socket.error as msg:
            raise XmatchCatError(msg)

    def get(self, out=sys.stdout):
        begin = True
        end = False
        if "COCAT_TEST" in os.environ:
            time.sleep(5)
        try:
            buf = ""
            while end == False:
                line = self.sock.recv(SOCKET_SIZE).decode()
                if not line:
                    raise XmatchCatError("connection is broken")

                if begin:
                    if self.noheader is False:
                        out.write("# catClient server {0}:{1}\n".format(self.host, self.port))
                begin = False

                complete_line = buf+line
                if complete_line.find("#+++ ERROR",0,10) == 0:
                    raise XmatchCatError(line)

                if complete_line.endswith("END\n"):
                    if not self.noheader :
                        out.write(line[0:len(line)-4]+"\n")
                    else :
                        out.write(line[0:len(line)-4])
                    end = True
                else: out.write(line)

                buf = self.last_line(line)

            if not self.noheader:
                out.write("\n")

        except OSError as e:
            # do not raise broken pipe error !
            if end is False and e.errno != errno.EPIPE: 
                raise XmatchCatError(str(e))
        except Exception as msg:
            if end is False:
                raise XmatchCatError(str(msg))

    def close(self):
        if self.__is_connected:
            self.sock.send(b" STOP ")
        XmatchCatCom.close(self)
        
# ------------------------------------------------------------------------------
class ASUparameters:
    """Manage ASU parameters
    """
    def __init__(self):
        """Constructor
        """

        # syntax of asuprm
        # key:default value
        # with default value=false for keys without values (ex -whole)
        #                   ="*" for arg completed by string (ex -lW2mag ,5)

        # accepted parameter and default values
        self.param = {
                # format
                "--noformat": False, "--format": DEFAULT_FORMAT, "--noheader":False,
                "-recno":None,"-print_recno":False,
                # geometry and query methods
                "--ipix": None, "-id":None, "--box": None, "-whole":False, "--test":False, "--ack": False,
                # other
                "--verbose": False,
                # ASU parameters
                "-source":None, "-c":None, "-c.r":str(DEFAULT_RADIUS), "-c.u":DEFAULT_RADIUS_UNIT,"-c.rs":None,
                "-c.rm":None,"-c.rd":None,"-out.max":"-1", 
                # output and constraints
                "-col":None, "-filter":None, "-l":"*", "-m":None,
                # connections 
                "--port": DEFAULT_PORT, "--host": "localhost", "--dir":None, "--mirror": None
            }

        self.__getArgs()

        if self.param["--ack"] is False and self.param["-source"] is None:
            raise XmatchCatError("-source required")

    def __getArgs(self):
        """ get the arguments and memorize them """
        i = 1
    
        while i < len(sys.argv):
            found = False
            
            if sys.argv[i] == "--help":
                print (__doc__)
                sys.exit(0)
            if sys.argv[i].find("--port") == 0:
                if len(sys.argv[i]) == 6:
                    i+=1
                    self.param["--port"] = int(sys.argv[i])
                elif sys.argv[i][6] == '=':
                    self.param["--port"] = int(sys.argv[i][7:])
                i+=1
                continue
            if sys.argv[i].find("--host") == 0:
                if len(sys.argv[i]) == 6:
                    i+=1
                    self.param["--host"] = sys.argv[i]
                elif sys.argv[i][6] == '=':
                    self.param["--host"] = sys.argv[i][7:]
                i+=1
                continue
            if sys.argv[i] == "--mirror" :
                self.param[sys.argv[i]] = True
                i += 1
                continue
            if sys.argv[i] == "--format" :
                val = sys.argv[i].split('=')
                if len(val) != 2:
                    if len(sys.argv) > i+1:
                        i += 1
                        val.append(sys.argv[i])
                    else:
                        raise Exception("--format needs a value")
                self.param["--format"] = val[1]
                i += 1
                continue
            if sys.argv[i] == "--noformat" :
                self.param[sys.argv[i]] = True
                i += 1
                continue
            if sys.argv[i] == "--noheader" :
                self.param[sys.argv[i]] = True
                i += 1
                continue
            if sys.argv[i].find("--ipix") == 0 :
                val = sys.argv[i].split('=')
                if len(val) != 2: 
                    if len(sys.argv) > i+1:
                        i += 1
                        val.append(sys.argv[i])
                    else:
                        raise Exception("--ipix needs a value")
                self.param[val[0]] = val[1]
                i += 1
                continue
            if sys.argv[i].find("-ip") == 0:
                self.param["-ip"] = sys.argv[i].split("=")[1]
    
            for arg in self.param.keys() :
                if sys.argv[i].find(arg) == 0:
                    if self.param[arg] == "*":
                        self.param[arg] = sys.argv[i][2:]+":"+sys.argv[i+1]
                        i+=1
    
                    elif len(sys.argv[i]) == len(arg):
                        if self.param[arg] == False:
                            self.param[arg] = True
                        else:
                            i+=1
                            self.param[arg] = sys.argv[i]
                    elif sys.argv[i][len(arg)] == '=':
                        self.param[arg] = sys.argv[i][len(arg)+1:]
    
                    else: continue
                        
                    found = True
                    break
            
            if not found:
                sys.stderr.write("****Unknown arg "+sys.argv[i]+"\n")
                print (__doc__)
                sys.exit(0)
            i+=1
    
    def toxmatchparam(self):
        """ASU parameters to Xmacth(bigcat server) parameter
        :return: string used for Xmatch server
        """
        if self.param["--noformat"]:
            param = "-noformat"
        else:
            param = ""
        if self.param["--noheader"]:
            param += " -nometa -noheader"
        if "-ip" in self.param:
            param += "  -ip "+self.param["-ip"]
        param += " -format "+self.param["--format"]+" -catName "+self.param['-source']

        # set the mode (recno, whole, cone, jzone, ipix, id, box)
        if self.param["-recno"]:
            if self.param["-recno"].find("..")>0:
                p = self.param["-recno"].split("..")
                param += " -mode recnorange -fromRecno {0} -nRecno {1}".format(p[0],p[1])
            else:
                param += " -mode recno -recno "+self.param['-recno']

        elif self.param["-whole"]:
            param += " -mode allsky"

        elif self.param["-c"]:
            posc = self.param['-c']
            mo = re.match("^(\d+) +(\d+) +([\d.]+) *([+-])(\d+) +(\d+) +([\d.]+)", posc)
            if mo:
                posc = "{0}:{1}:{2}{3}{4}:{5}:{6}".format(mo.group(1),mo.group(2),mo.group(3),mo.group(4),mo.group(5),mo.group(6),mo.group(7))

            exp = re.compile("J\d+[+-]\d+")
            if exp.search(posc):
                param += " -mode jzone -jzone "+posc
            else:
                if float(self.param['-c.r']) < 0.001:
                    sys.stderr.write("****radius must be >0\n")
                    sys.exit(1) 
                param += " -mode cone -pos "+posc+" -rarcmin "+self.param['-c.r']

        elif self.param["--ipix"]:
            ipix = re.split("[:/]", self.param["--ipix"])
            param += " -mode healpix -order {0} -ipix {1}".format(ipix[0], ipix[1]) 

        elif self.param["-id"]:
            param += " -mode id -id "+self.param['-id']

        elif self.param["--box"]:
            pos = self.param["--box"].split(",")
            param += " -mode zone -minRA {0} -maxRA {1} -minDec {2} -maxDec {3}".format(pos[0],pos[2],pos[1], pos[3])

        else:
            param += " -mode cone "

        # other options
        if self.param["-out.max"]:
            if int(self.param["-out.max"]) > 0:
                param += " -limit "+self.param['-out.max']

        if self.param["-print_recno"]:
            param += " -print_recno true"

        if self.param["-col"]:
            exp = re.compile(",*recno,*")
            if exp.search(self.param["-col"]):
                param += " -print_recno true"
            param += " -col "+self.param["-col"]

        if self.param["-filter"]:
            param += " -filter "+self.param["-filter"]
    
        return param


    def adjustArgs(self, ishttp=False):
        """ adjust the memorized arguments (see: -c.* -l) """
    
        # adjust positions arguments 
        if self.param["-c.rs"]:
            self.param["-c.u"] = "sec"
            self.param["-c.r"] = self.param["-c.rs"]
        if self.param["-c.rm"]:
            self.param["-c.u"] = "min"
            self.param["-c.r"] = self.param["-c.rm"]
        if self.param["-c.rd"]:
            self.param["-c.u"] = "deg"
            self.param["-c.r"] = self.param["-c.rd"]

        if self.param["-c.u"] == "sec":
            self.param["-c.r"] = str(float(self.param["-c.r"])/60.)
        elif self.param["-c.u"] == "deg":
            self.param["-c.r"] = str(float(self.param["-c.r"])*60.)
    
        # translate -l arguments into -filter args
        if self.param["-l"] != "*":
            p = self.param["-l"].split(":")

            if ishttp is False:
                self.param["-filter"] = p[0]
                if len(p)>1 :
                    if p[1].find(",")>-1:
                        v=p[1].split(",")
                        if v[0]:
                            self.param["-filter"] += ">"+v[0]
                            if v[1]: self.param["-filter"] += " && "+p[0]+"<"+v[1]
                        elif v[1]:
                            self.param["-filter"] += "<"+v[1]
                        else:
                            self.param["-filter"] = "!Double.isNaN("+p[0]+")"
                    else:  self.param["-filter"] += "="+p[1]
                    del(self.param["-l"])
            else:
                self.param["-l"+p[0]] = p[1]
                del(self.param["-l"])

        if "limit" in self.param:
             self.param["-out.max"] = self.param["limit"]
        elif self.param["-m"] :
             self.param["-out.max"] = self.param["-m"]

        if self.param["--mirror"]:
             self.param["--dir"]='mirror'

        if self.param["-filter"]: 
            mo = re.match("^.*recno==([^&]*).*", self.param['-filter'])
            if mo:
                recno = mo.group(1)
                sys.stderr.write("----catClient:recno="+str(recno)+"\n")

                self.param['-filter'] = re.sub("^.*(recno==[^&]*).*","",self.param['-filter'])
                self.param['-recno'] = recno

        if "REMOTE_ADDR" in os.environ:
            if "-ip" not in self.param:
                self.param["-ip"] = os.environ["REMOTE_ADDR"]

# ------------------------------------------------------------------------------
def test(asu, com):
    """ test a catalog
    :param asu: ASU parameters
    :param com: connection handle
    :return: True/False (if error)
    """
    try:
        com.connect(5)

    except XmatchCatError as msg:
        sys.stderr.write("****catClient.test : {0}\n".format(msg.msg))
        return False

    try:
        if "-source" not in asu.param:
            raise Exception("-source parameter is requires with --test")

        if isinstance(com, XmatchCatComSock):
            com.send(message="-mode test -format csv -catName "+asu.param["-source"])
        else:
            com.send(asu)

        com.get()
        com.close()

    except XmatchCatError as msg:
        com.close()
        sys.stderr.write("****catClient.test : {0}\n".format(msg.msg))
        return False

    return True
          
# ------------------------------------------------------------------------------
def ack(asu, com):
    """send ACK message
    :param asu: ASU parameters
    :param com: connection handle

    Note: exit program (test only current connection!)
    """
    try:
        # set monitor port
        if not com.port : com.port = DEFAULT_PORT_MONITOR
        com.port += 10 
        com.connect(5)

    except XmatchCatError as msg:
        sys.stderr.write("****catClient.ack :"+msg.msg+"\n")  
        sys.exit(1)

    try:
        signal.alarm(3)
        nb = com.ack()
        if nb < MIN_FREE_CONNECTIONS:
            if asu.param["--verbose"]:
                sys.stderr.write("----catClient.ack : nb free connection ={0} < {1}\n".format(nb, MIN_FREE_CONNECTIONS))
            sys.exit(2)

        if asu.param["--verbose"]:
            sys.stderr.write("----catClient.ack : nb free connection ={0} < {1}\n".format(nb, MIN_FREE_CONNECTIONS))

        sys.exit(0)

    except XmatchCatError as msg:
        com.close()
        sys.stderr.write("****catClient.ack : "+msg.msg+"\n")  
        sys.exit(1)

# ------------------------------------------------------------------------------
def glu(tag):
    """resolve GLU tag"""
    option = ""
    if "GLUDIR" in os.environ:
        option = "-H {0}/".format(os.environ["GLUDIR"])
    
    try:
        p = os.popen('glufilter -r "<&{0}>" {1}'.format(tag, option))
        lines = p.read().strip()
        if lines == tag: 
            return None
        p.close()
        return lines

    except Exception as e:
        return None

# ------------------------------------------------------------------------------
def has_local(catalog):
    """check if catalog is in local"""
    if not os.getenv("Vroot"): return False
    fname = "{0}/bincats/{1}.rcf".format(os.getenv("Vroot"), catalog.lower())
    if os.path.exists(fname):
        return True
    return False

# ------------------------------------------------------------------------------
if __name__ == "__main__":

    # memorize arguments
    asu = ASUparameters()

    # local or remote ?
    # rules when --mirror - try the folowing connexions
    # 1) if catalogue is local then (localhost,1800),(COCAT:COCATPORT)
    # 2) resolve glu VizieR.QueryCat
    # 3) DEFAULT_SERVER:80
    connexions = []

    mirror_connexion = False 
    if asu.param["--dir"]:
        if asu.param["--dir"] == 'mirror' : mirror_connexion =True
    elif asu.param["--mirror"]: mirror_connexion =True

    if mirror_connexion:
        if has_local(asu.param["-source"]):
            asu.param["--dir"] = os.getenv("Vroot")+"/bincats"

            # use default server designed by Var Env. 
            if os.getenv("COCAT"): host = os.getenv("COCAT")
            else: host = DEFAULT_SERVER
 
            if os.getenv("COCATPORT"): 
                port = int(os.getenv("COCATPORT"))
            else: port = DEFAULT_PORT

            connexions.append(("localhost", DEFAULT_PORT))
            connexions.append((host, port))

        querycat = glu("VizieR.QueryCat")
        if querycat:
            p = querycat.split(":") 
            connexions.append((p[0], int(p[1])))
        
        if (DEFAULT_SERVER, 80) not in connexions:
            connexions.append((DEFAULT_SERVER, 80))

    else:
        if asu.param["--host"]: host =  asu.param["--host"]
        else: host = DEFAULT_SERVER

        if asu.param["--port"]: port =  asu.param["--port"]
        else: port = DEFAULT_PORT

        connexions.append((host, port))

    noheader = None
    if "--noheader" in asu.param: noheader = asu.param["--noheader"]

    error = None
    for host,port in connexions:
        if asu.param["--verbose"]:
            sys.stderr.write("----catClient connect {0}:{1}\n".format(host,port))

        if port in (80, 443) :
            # HTTP connection
            com = XmatchCatComHTTP(port, host, noheader)
            asu.adjustArgs(ishttp=True)
        else:
            com = XmatchCatComSock(port, host, noheader)
            asu.adjustArgs()

        # exit when missed arguments 
        if asu.param["--test"]:
            if test(asu, com) is True: sys.exit(0)

        # exit when missed arguments 
        if asu.param["--ack"]:
            ack(asu, com)
            sys.exit(0)

        # send (and print) the request to the Xmatch Catalog server
        try:
            if "COCAT_TIMEOUT" in os.environ:
                com.connect(int(os.environ["COCAT_TIMEOUT"]))
            else:
                com.connect()
            com.send(asu)
        except Exception as msg:
            error = str(msg)
            sys.stderr.write("****catClient fails: "+error+"\n")
            try:
                com.close()
            except:
                 pass
            continue

        try:
            com.get()
            com.close()
            sys.exit(0)
        except Exception as msg:
            error = str(msg)
            try:
                com.close()
            except:
                 pass
            break;

    print("****catClient fails:"+error+"\n")
    sys.exit(1)

