#@+leo-ver=5-thin
#@+node:ville.20091010232339.6117: * @file ../external/lproto.py
#@+<< docstring >>
#@+node:ville.20091010205847.1364: ** << docstring >>
""" lproto - simple local socket protocol dispatcher (reactor) for PyQt

Author: Ville M. Vainio <vivainio@gmail.com>

"""
#@-<< docstring >>
#@+<< imports >>
#@+node:ville.20091009234538.1373: ** << imports >>
# todo remove dependency on Qt.
import leo.core.leoGlobals as g
from leo.core.leoQt import isQt5,QtCore
if isQt5:
    from PyQt5 import QtNetwork
else:
    from PyQt4 import QtNetwork
import os
import socket
import struct




#@-<< imports >>
#@@killbeautify
# EKR: use this by default.
if hasattr(socket,'AF_UNIX'):
    standard_leo_socket_name = os.path.expanduser('~/.leo/leoserv_sockname')
else:
    standard_leo_socket_name = '(172.16.0.0:1)' # A hack.

#@+others
#@+node:tbrown.20130319124904.18711: ** lprint
def lprint(*args):
    """lprint "Log Print" - print args

    To replace all the print() statements so the can be
    easily dis/enabled

    :Parameters:
    - `*args`: args to print
    """

    # print(args)

    return
#@+node:ville.20091010205847.1363: ** sending
def mk_send_bytes(msg):

    lendesc = struct.pack('I', len(msg))
    return lendesc + msg

#@+node:ville.20091010205847.1362: ** class LProtoBuf
class LProtoBuf(object):
    def __init__(self):

        self.plen = -1
        self.buf = ""

    def set_recv_cb(self, cb):
        """ set func to call with received messages """
        self.recv_cb = cb
    def get_rlen(self):
        # read pkg length
        if self.plen == -1:
            return 4
        return self.plen - len(self.buf)

    def push_bytes(self, allbytes):
        while allbytes:
            rlen = self.get_rlen()
            byts = allbytes[0:rlen]
            self.push_bytes_one(byts)
            allbytes = allbytes[rlen:]

    def push_bytes_one(self, byts):
        if self.plen == -1:
            lendesc = byts[0:4]
            intlen = struct.unpack('I', lendesc)[0]
            lprint("have", intlen, "bytes")
            self.plen = intlen
            self.buf = byts[4:]
        else:
            self.buf = self.buf + byts

        if len(self.buf) == self.plen:
            lprint("dispatch msg", self.buf)
            self.recv_cb(self.buf)
            self.buf = ""
            self.plen = -1
            return

        lprint("in buf",self.buf)
#@+node:ville.20091009234538.1374: ** class LProtoServer
class LProtoServer(object):

    #@+others
    #@+node:ekr.20111012070545.7254: *3* __init__ (LProtoServer)
    def __init__(self):

        self.srv = QtNetwork.QLocalServer()
        self.receiver = None
        self.ses = {}

        self.is_connected = self.srv.connect(
            self.srv,
            QtCore.SIGNAL("newConnection()"),
            self.connected)
    #@+node:ekr.20111012070545.7255: *3* listen
    def listen(self,name):

        # g.trace(name,g.callers())

        self.srv.listen(name)

        lprint("lproto.py: listen on",self.srv.fullServerName())

    #@+node:ekr.20111012070545.7256: *3* msg_received
    def msg_received(self,msg,ses):

        if self.receiver:
            self.receiver(msg,ses)
    #@+node:ekr.20111012070545.7257: *3* set_receiver
    def set_receiver(self, receiver):

        self.receiver = receiver
    #@+node:ekr.20111012070545.7258: *3* connected
    def connected(self):

        '''Event handler for newConnection.'''

        lprint("hnd con")
        lsock = self.srv.nextPendingConnection()
        lprint("conn", lsock)

        buf =  LProtoBuf()

        self.ses[lsock] = ses_ent = {'_sock': lsock, '_buf': buf}

        def msg_recv_cb(msg):
            self.msg_received(msg, ses_ent)

        buf.set_recv_cb( msg_recv_cb )

        def readyread_cb():
            lprint("read ready")
            allbytes = lsock.readAll()
            buf = ses_ent['_buf']
            buf.push_bytes(allbytes)

        lsock.connect(lsock,
            QtCore.SIGNAL('readyRead()'),
            readyread_cb)
        #self.connect(self.qsock, SIGNAL('connectionClosed()'), self.handleClosed)
    #@+node:ekr.20111012091630.9385: *3* readyread
    def readyread(self):
        pass
    #@-others
#@+node:ville.20091010205847.1360: ** (ignore) class LProtoObsoleteClient
if 0:
    class LProtoObsoleteClient:
        #@+others
        #@+node:ville.20091010205847.1361: *3* initialization (LProtoObsoleteClient)
        def __init__(self):
            self.cl = QtNetwork.QLocalSocket()

        def connect(self,name):
            self.cl.connectToServer(name)
            lprint("client connected")
        #@-others
#@+node:ville.20091010233144.10051: ** class LProtoClient
class LProtoClient(object):

    #@+others
    #@+node:ekr.20111012070545.7210: *3* ctor (LProtoClient)
    def __init__(self,fname=standard_leo_socket_name):

        self.socket_name = fname

        self.is_connected = self.connect(fname)

        if self.is_connected:
            self.recvbuf = LProtoBuf()
        else:
            self.recvbuf = None
    #@+node:ekr.20111012070545.7212: *3* connect
    def connect (self,fname):

        '''Connect to the server.  Return True if the connection was established.'''

        trace = False and not g.unitTesting

        if trace: g.trace(fname,socket)

        if hasattr(socket,'AF_UNIX'):
            try:
                # pylint: disable=no-member
                # Module 'socket' has no 'AF_UNIX' member
                self.socket = socket.socket(socket.AF_UNIX,socket.SOCK_STREAM)
                self.socket.connect(fname)
                return True
            except Exception:
                g.es_print('lproto.py: failed to connect!',fname)
                g.es_exception(full=False,c=None)
                return False
        else:
            try:
                host = '172.16.0.0' # host is a local address.
                port = 1
                self.socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
                self.socket.connect((host,port),)
                self.socket.connect(fname)
                return True
            except Exception:
                g.es_print('lproto.py: failed to connect! host: %s, port: %s' % (
                    host,port))
                g.es_exception(full=False,c=None)
                return False
    #@+node:ekr.20111012070545.7211: *3* send
    def send(self,msg):

        byts = mk_send_bytes(msg)
        self.socket.sendall(byts)
    #@-others


#@-others
#@@language python
#@@tabwidth -4
#@@pagewidth 70
#@-leo
