# Copyright (c) 2000 Phil Thompson <phil@river-bank.demon.co.uk>


import socket
from qt import SIGNAL, PYSIGNAL, QObject, QSocketNotifier


def AsyncPendingWrite(file):
    """AsyncPendingWrite(file) -> boolean

    Return non-zero if the file object has data waiting to be written.

    """
    try:
        pending = file.pendingWrite()
    except:
        pending = 0

    return pending


class AsyncFile:
    """AsyncFile(self,sock,mode,name)

    A class that wraps a socket object with a file object interface.  sock is
    the socket object being wrapped.  mode is the value to set the mode
    attribute to.  name is the value to set the name attribute to.

    """
    def __init__(self,sock,mode,name):
        # Initialise the attributes.
        self.closed = 0
        self.sock = sock
        self.mode = mode
        self.name = name
        self.softspace = 0

        self.wpending = ''

    def checkMode(self,mode):
        """
        Private method to check that an operation is permitted according to the
        mode of the file.
        """
        if mode != self.mode:
            raise IOError, '[Errno 9] Bad file descriptor'

    def nWrite(self,n):
        """
        Private method to write a specific number of pending bytes.
        """
        if n:
            sent = self.sock.send(self.wpending[:n])
            self.wpending = self.wpending[sent:]

    def pendingWrite(self):
        """
        Public method that returns the number of bytes waiting to be written.
        """
        return self.wpending.rfind('\n') + 1

    def close(self):
        if not self.closed:
            self.flush()
            self.sock.close()
            self.closed = 1

    def flush(self):
        self.nWrite(len(self.wpending))

    def isatty(self):
        return 0

    def fileno(self):
        return self.sock.fileno()

    def read(self,size=-1):
        self.checkMode('r')

        if size < 0:
            size = 20000

        return self.sock.recv(size)

    def readline(self,size=-1):
        # Note that this method will not block and may return only a part of a
        # line if that is all that is available.
        self.checkMode('r')

        if size < 0:
            size = 20000

        # The integration of the debugger client event loop and the connection
        # to the debugger relies on the two lines of the debugger command being
        # delivered as two separate events.  Therefore we make sure we only
        # read a line at a time.
        line = self.sock.recv(size,socket.MSG_PEEK)

        eol = line.find('\n')

        if eol >= 0:
            size = eol + 1
        else:
            size = len(line)

        # Now we know how big the line is, read it for real.
        return self.sock.recv(size)

    def readlines(self,sizehint=-1):
        lines = []
        room = sizehint

        line = self.readline(room)
        linelen = len(line)

        while linelen > 0:
            lines.append(line)

            if sizehint >= 0:
                room = room - linelen

                if room <= 0:
                    break

            line = self.readline(room)
            linelen = len(line)

        return lines

    def seek(self,offset,whence=0):
        raise IOError, '[Errno 29] Illegal seek'

    def tell(self):
        raise IOError, '[Errno 29] Illegal seek'

    def truncate(self,size=-1):
        raise IOError, '[Errno 29] Illegal seek'

    def write(self,str):
        self.checkMode('w')
        self.wpending = self.wpending + str
        self.nWrite(self.pendingWrite())

    def writelines(self,list):
        map(self.write,list)


class AsyncIO(QObject):
    """AsyncIO(self,parent=None)

    A class that implements asynchronous reading and writing using the Qt event
    loop.  parent is the optional parent QObject.  The lineReady signal is
    emitted when a complete line has been read.  The gotEOF signal is emitted
    if EOF was read.

    """
    def __init__(self,parent=None):
        QObject.__init__(self,parent)

        # There is no connection yet.
        self.disconnect()

    def disconnect(self):
        """
        Public method to disconnect any current connection.
        """
        self.readsn = None
        self.readfd = None
        self.writesn = None
        self.writefd = None

    def setDescriptors(self,rfd,wfd):
        """
        Public method called to set the descriptors for the connection.
        """
        self.rbuf = ''
        self.readfd = rfd

        self.wbuf = ''
        self.writefd = wfd

    def setNotifiers(self):
        """
        Public method called to set up the socket notifiers for the Qt event
        loop.
        """
        self.readsn = QSocketNotifier(self.readfd.fileno(),QSocketNotifier.Read)
        self.readsn.connect(self.readsn,SIGNAL('activated(int)'),self.readReady)

        self.writesn = QSocketNotifier(self.writefd.fileno(),QSocketNotifier.Write)
        self.writesn.connect(self.writesn,SIGNAL('activated(int)'),self.writeReady)
        self.setWriteNotifier()

    def readReady(self,fd):
        """
        Protected method called when there is data ready to be read.
        """
        # There seems to be a problem under Windows that the QSocketNotifier
        # says that there is data from the server, but the Python socket module
        # complains that the recv() on the socket would block and raises an
        # exception.  However, everything goes on to work as expected so we
        # just catch the exception and ignore it.
        try:
            got = self.readfd.readline()
        except:
            return

        if len(got) == 0:
            self.emit(PYSIGNAL('gotEOF'),())
            return

        self.rbuf = self.rbuf + got

        # Emit a signal for the line if it is complete.
        eol = self.rbuf.find('\n')

        if eol >= 0:
            s = self.rbuf[:eol + 1]
            self.rbuf = self.rbuf[eol + 1:]
            self.emit(PYSIGNAL('lineReady'),(s,))

    def writeReady(self,fd):
        """
        Protected method called when we are ready to write data.
        """
        self.writefd.write(self.wbuf)
        self.writefd.flush()
        self.wbuf = ''

        if self.writesn is not None:
            self.setWriteNotifier()

    def setWriteNotifier(self):
        """
        Private method called to disable the write notifier if there is
        nothing to be written.
        """
        if not AsyncPendingWrite(self.writefd):
            self.writesn.setEnabled(0)

    def write(self,s):
        """
        Public method to write a string.
        """
        # Make sure any write notifier is enabled.
        if self.writesn is not None:
            self.writesn.setEnabled(1)

        self.wbuf = self.wbuf + s
