#       m   
#      u    _common.py - Sat Sep 07 19:04 CEST 2013
#  SQLite   socket connection
#    d      part of sqmediumlite
#   e       copyright (C): nobody
#  m        

"""
Socket interface, connecting front-end to back-end.
Plus some other common attributes.
"""

import sys
if sys.platform == "win32" and "socket" not in sys.modules:
    from socket import setdefaulttimeout
    setdefaulttimeout (7.5) # keyboard-interruptable on Windows too
from socket import SHUT_RD, SO_REUSEADDR, SOL_SOCKET, SocketType, error
from marshal import loads, dumps

version = "2.1.6" # sqmediumlite version

class Error (Exception):
    " generic DB-API error "
class ServerNotUp (Error):
    " fail to connect to back-end "
class PicklingError (Error):
    " data can not be flattened for transport over sockets "

def e_str (e):
    " format exception as string "
    return "%s: %s" % (e.__class__.__name__, e)

def print23 (*args, **kwargs):
    """ python3 style print function """
    kwargs.pop ("file", sys.stdout).write (
            ' '.join (map (str, args)) + kwargs.pop ("end", "\n")
            )

class Socket (SocketType):
    """
    Adapts a socket to sqmediumlite front-end back-end interface.
    Raises ServerNotUp and closes the socket in case any network 
    error occurs.
    """
    def sconnect (self, address):
        try:
            self.connect (address)
        except error as e:
            raise ServerNotUp ("error connecting to %s:%s" % (address, e))
        return self
    def sbindlisten (self, address):
        self.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
        try:
            self.bind (address)
        except Exception as e:
            self.close ()
            raise ServerNotUp ("%s (binding to %s)" % (e, address))
        self.listen (2) # default 3 in the pipeline
        return self
    def saccept (self):
        while True: # while next connection or timeout
            try:
                # cast accepted connection as subclass
                if sys.version < '3':
                    return Socket(_sock=self._sock.accept() [0])
                else: # Python3
                    return Socket(fileno=self._accept() [0])
            except Exception as e:
                if not _is_retriable (e):
                    self.close ()
                    raise ServerNotUp ("accept error " + e_str (e))
    def srecv (self):
        data = None
        while True:
            try:
                if data is None:
                    data = self.recv (1500)
                else:
                    data2 = self.recv (150000)
                    if not data2:
                        self.close() # fail in next cycle
                    data += data2
            except Exception as e:
                if not _is_retriable (e):
                    self.close()
                    raise ServerNotUp ( "recv error " + e_str (e))
                continue
            try:
                return loads (data)
            except EOFError: # need more data
                pass
            except ValueError as e: # may need more data
                if "unknown type code" in str (e):
                    raise PicklingError (
                            "received unexpected data in Python %.3s" % sys.version)
    def ssend (self, d):
        try:
            data = dumps (d)
        except Exception as e:
            raise PicklingError (str (e))
        while data:
            try:
                data = data [self.send (data):]
            except Exception as e:
                if not _is_retriable (e):
                    self.close()
                    raise ServerNotUp ( "send error " + e_str (e))
    def sshutdown (self):
        try:
            self.shutdown (SHUT_RD) # 2 == SHUT_RDWR
        except error as e:
            self.close()
            raise ServerNotUp ( "shutdown error " + e_str (e))
    def sgetpeername (self):
        try:
            return self.getpeername ()
        except error as e: # socket being closed
            return (str (e), -1) # may appear in status()
# internal methods
def _is_retriable (e):
    " see if socket error is a timeout "
    return isinstance (e, error) and (
            "timed out" in str (e) or "temporarily" in str (e))