# -*- coding: utf-8 -*-

# Copyright (c) 2002 - 2006 Detlev Offenbach <detlev@die-offenbachs.de>
# Copyright (c) 2000 - 2002 Phil Thompson <phil@river-bank.demon.co.uk>
#

"""
Module implementing the debug server.
"""

import sys
import os
import socket
import time
import signal

from qt import SIGNAL, PYSIGNAL, qApp, QTextCodec
from qtnetwork import QServerSocket, QSocket

from DebugClients.Python.DebugProtocol import *
from Config import ClientDefaultCapabilities, ClientTypeAssociations
import Preferences
import Utilities

from eric3config import getConfig


class DebugServer(QServerSocket):
    """
    Class implementing the debug server embedded within the IDE.
    
    @signal clientOutput emitted after the client has sent some output
    @signal clientRawInputSent emitted after the data was sent to the debug client
    @signal clientLine(filename, lineno) emitted after the debug client has executed a
            line of code
    @signal clientStack(stack) emitted after the debug client has executed a
            line of code
    @signal clientVariables(variables) emitted after a variables dump has been received
    @signal clientVariable(variables) emitted after a dump for one class variable has
            been received
    @signal clientStatement(boolean) emitted after an interactive command has
            been executed. The parameter is 0 to indicate that the command is
            complete and 1 if it needs more input.
    @signal clientException(exception) emitted after an exception occured on the 
            client side
    @signal clientSyntaxError(exception) emitted after a syntax error has been detected
            on the client side
    @signal clientExit(exitcode) emitted after the client has exited
    @signal clientClearBreak(filename, lineno) emitted after the debug client
            has decided to clear a temporary breakpoint
    @signal clientClearWatch(condition) emitted after the debug client
            has decided to clear a temporary watchpoint
    @signal clientRawInput(prompt, echo) emitted after a raw input request was received
    @signal clientBanner(banner) emitted after the client banner was received
    @signal clientCapabilities(capabilities, cltype) emitted after the clients capabilities were
            received
    @signal clientCompletionList(completionList, text) emitted after the client
            the commandline completion list and the reworked searchstring was
            received from the client
    @signal passiveDebugStarted emitted after the debug client has connected in
            passive debug mode
    @signal clientGone emitted if the client went away (planned or unplanned)
    @signal utPrepared(nrTests, exc_type, exc_value) emitted after the client has
            loaded a unittest suite
    @signal utFinished emitted after the client signalled the end of the unittest
    @signal utStartTest(testname, testdocu) emitted after the client has started 
            a test
    @signal utStopTest emitted after the client has finished a test
    @signal utTestFailed(testname, exc_info) emitted after the client reported 
            a failed test
    @signal utTestErrored(testname, exc_info) emitted after the client reported 
            an errored test
    @signal cyclopsError(fn, modfunc) emitted after the client reported an error
            while performing a Cyclops run
    """
    def __init__(self):
        """
        Constructor
        """
        if Preferences.getDebugger("PassiveDbgEnabled"):
            socket = Preferences.getDebugger("PassiveDbgPort") # default: 42424
            QServerSocket.__init__(self,socket)
            self.passive = 1
            self.passiveClientExited = 0
            # set translation function
            if Preferences.getDebugger("PathTranslation"):
                self.translateRemote = \
                    unicode(Preferences.getDebugger("PathTranslationRemote"))
                self.translateLocal = \
                    unicode(Preferences.getDebugger("PathTranslationLocal"))
                self.translate = self.__remoteTranslation
            else:
                self.translate = self.__identityTranslation
        else:
            QServerSocket.__init__(self,0)
            self.passive = 0
            # set translation function
            self.translate = self.__identityTranslation

        self.qsock = None
        self.debugging = 1
        self.queue = []
        self.clientPID = 0
        self.clientType, ok = Preferences.Prefs.settings.readEntry(\
            '/eric3/DebugClient/Type', 'Python')
        self.lastClientType = ''
        # set default values for capabilities of clients
        self.clientCapabilities = {
            "Python" : ClientDefaultCapabilities["Python"],
            "Ruby"   : ClientDefaultCapabilities["Ruby"],
        }
        
        self.connect(self, PYSIGNAL("clientCapabilities"), self.handleClientCapabilities)

        self.codec = QTextCodec.codecForName(sys.getdefaultencoding())

    def __identityTranslation(self, fn, remote2local=1):
        """
        Private method to perform the identity path translation.
        
        @param fn filename to be translated (string or QString)
        @param remote2local flag indicating the direction of translation
            (0 = local to remote, 1 = remote to local [default])
        @return translated filename (string)
        """
        return unicode(fn)
        
    def __remoteTranslation(self, fn, remote2local=1):
        """
        Private method to perform the path translation.
        
        @param fn filename to be translated (string or QString)
        @param remote2local flag indicating the direction of translation
            (0 = local to remote, 1 = remote to local [default])
        @return translated filename (string)
        """
        if remote2local:
            return unicode(fn).replace(self.translateRemote, self.translateLocal)
        else:
            return unicode(fn).replace(self.translateLocal, self.translateRemote)
        
    def startRemote(self, forProject=0):
        """
        Public method to start a remote interpreter.
        @param forProject flag indicating a project related action (boolean)
        """
        # only start the client, if we are not in passive mode
        if self.passive:
            return
            
        if self.clientPID:
            try:
                # get rid of died child on Unix systems; if the child is
                # non cooperative, it will be killed
                status = os.waitpid(self.clientPID, os.WNOHANG)
                if status == (0, 0):
                    os.kill(self.clientPID, signal.SIGTERM)
                    time.sleep(1)
                    status = os.waitpid(self.clientPID, os.WNOHANG)
                    if status == (0, 0):
                        os.kill(self.clientPID, signal.SIGKILL)
            except:
                pass
            
        if forProject:
            self.startRemoteForProject()
        else:
            if self.clientType == "Ruby":
                self.startRemoteRuby()
            else:
                self.startRemotePython()
        
    def startRemotePython(self):
        """
        Private method to start a remote Python interpreter.
        """
        if Preferences.getDebugger("CustomPythonInterpreter"):
            interpreter = str(Preferences.getDebugger("PythonInterpreter"))
            if interpreter == "":
                interpreter = sys.executable
        else:
            interpreter = sys.executable
            
        debugClientType = Preferences.getDebugger("DebugClientType")
        if debugClientType == 1:
            debugClient = os.path.join(getConfig('ericDir'), 
                                       "DebugClients", "Python", "DebugClient.py")
        elif debugClientType == 2:
            debugClient = os.path.join(getConfig('ericDir'),
                                       "DebugClients", "Python", "DebugClientThreads.py")
        elif debugClientType == 3:
            debugClient = os.path.join(getConfig('ericDir'),
                                       "DebugClients", "Python", "DebugClientNoQt.py")
        else:
            debugClient = str(Preferences.getDebugger("DebugClient"))
            if debugClient == "":
                debugClient = os.path.join(sys.path[0],
                                           "DebugClients", "Python", "DebugClient.py")
            
        redirect = str(Preferences.getDebugger("PythonRedirect"))
        noencoding = \
            Preferences.getDebugger("PythonNoEncoding") and '--no-encoding' or ''
        
        if Preferences.getDebugger("RemoteDbgEnabled"):
            ipaddr = socket.gethostbyname(socket.gethostname())
            rexec = str(Preferences.getDebugger("RemoteExecution"))
            rhost = str(Preferences.getDebugger("RemoteHost"))
            if rhost == "":
                rhost = "localhost"
            if rexec:
                args = Utilities.parseOptionString(rexec) + \
                       [rhost, interpreter, os.path.abspath(debugClient),
                        noencoding, `self.port()`, redirect, ipaddr]
                args[0] = Utilities.getExecutablePath(args[0])
                self.clientPID = os.spawnv(os.P_NOWAIT, args[0], args)
                # set translation function
                if Preferences.getDebugger("PathTranslation"):
                    self.translateRemote = \
                        unicode(Preferences.getDebugger("PathTranslationRemote"))
                    self.translateLocal = \
                        unicode(Preferences.getDebugger("PathTranslationLocal"))
                    self.translate = self.__remoteTranslation
                else:
                    self.translate = self.__identityTranslation
                return
        
        # set translation function
        self.translate = self.__identityTranslation
        
        # setup the environment for the debugger
        if Preferences.getDebugger("DebugEnvironmentReplace"):
            clientEnv = {}
        else:
            clientEnv = os.environ.copy()
        envlist = Utilities.parseEnvironmentString(\
            Preferences.getDebugger("DebugEnvironment"))
        for el in envlist:
            try:
                key, value = el.split('=', 1)
                if value.startswith('"') or value.startswith("'"):
                    value = value[1:-1]
                clientEnv[unicode(key)] = unicode(value)
            except UnpackError:
                pass
        
        if Preferences.getDebugger("ConsoleDbgEnabled"):
            ccmd = unicode(Preferences.getDebugger("ConsoleDbgCommand"))
            if ccmd:
                args = Utilities.parseOptionString(ccmd) + \
                       [interpreter, os.path.abspath(debugClient),
                        noencoding, `self.port()`, '0']
                args[0] = Utilities.getExecutablePath(args[0])
                self.clientPID = os.spawnve(os.P_NOWAIT, args[0], args, clientEnv)
                return
        
        if sys.platform == "win32":
            self.clientPID = os.spawnve(os.P_NOWAIT,interpreter,
                ['"%s"' % interpreter, '"%s"' % debugClient, 
                 noencoding, `self.port()`, redirect], clientEnv)
        else:
            self.clientPID = os.spawnve(os.P_NOWAIT,interpreter,
                [interpreter, debugClient, 
                 noencoding, `self.port()`, redirect], clientEnv)

    def startRemoteRuby(self):
        """
        Private method to start a remote Ruby interpreter.
        """
        interpreter = str(Preferences.getDebugger("RubyInterpreter"))
        if interpreter == "":
            interpreter = "/usr/bin/ruby"
        debugClient = os.path.join(getConfig('ericDir'), 
            "DebugClients", "Ruby", "DebugClient.rb")
        
        redirect = str(Preferences.getDebugger("RubyRedirect"))
        
        if Preferences.getDebugger("RemoteDbgEnabled"):
            ipaddr = socket.gethostbyname(socket.gethostname())
            rexec = str(Preferences.getDebugger("RemoteExecution"))
            rhost = str(Preferences.getDebugger("RemoteHost"))
            if rhost == "":
                rhost = "localhost"
            if rexec:
                args = Utilities.parseOptionString(rexec) + \
                       [rhost, interpreter, os.path.abspath(debugClient),
                        `self.port()`, redirect, ipaddr]
                args[0] = Utilities.getExecutablePath(args[0])
                self.clientPID = os.spawnv(os.P_NOWAIT, args[0], args)
                # set translation function
                if Preferences.getDebugger("PathTranslation"):
                    self.translateRemote = \
                        unicode(Preferences.getDebugger("PathTranslationRemote"))
                    self.translateLocal = \
                        unicode(Preferences.getDebugger("PathTranslationLocal"))
                    self.translate = self.__remoteTranslation
                else:
                    self.translate = self.__identityTranslation
                return
        
        # set translation function
        self.translate = self.__identityTranslation
        
        # setup the environment for the debugger
        if Preferences.getDebugger("DebugEnvironmentReplace"):
            clientEnv = {}
        else:
            clientEnv = os.environ.copy()
        envlist = Utilities.parseEnvironmentString(\
            Preferences.getDebugger("DebugEnvironment"))
        for el in envlist:
            try:
                key, value = el.split('=', 1)
                if value.startswith('"') or value.startswith("'"):
                    value = value[1:-1]
                clientEnv[unicode(key)] = unicode(value)
            except UnpackError:
                pass
        
        if Preferences.getDebugger("ConsoleDbgEnabled"):
            ccmd = unicode(Preferences.getDebugger("ConsoleDbgCommand"))
            if ccmd:
                args = Utilities.parseOptionString(ccmd) + \
                       [interpreter, os.path.abspath(debugClient),
                        `self.port()`, '0']
                args[0] = Utilities.getExecutablePath(args[0])
                try:
                    self.clientPID = os.spawnve(os.P_NOWAIT, args[0], args, clientEnv)
                except OSError:
                    pass
                return
        
        try:
            if sys.platform == "win32":
                self.clientPID = os.spawnve(os.P_NOWAIT,interpreter,
                    ['"%s"' % interpreter, '"%s"' % debugClient, `self.port()`, redirect], clientEnv)
            else:
                self.clientPID = os.spawnve(os.P_NOWAIT,interpreter,
                    [interpreter, debugClient, `self.port()`, redirect], clientEnv)
        except OSError:
            pass

    def startRemoteForProject(self):
        """
        Private method to start a remote interpreter for a project.
        """
        project = qApp.mainWidget().getProject()
        if not project.isDebugPropertiesLoaded():
            if self.clientType == "Ruby":
                self.startRemoteRuby()
            else:
                self.startRemotePython()
            return
        
        # start debugger with project specific settings
        interpreter = project.getDebugProperty("INTERPRETER")
        debugClient = project.getDebugProperty("DEBUGCLIENT")
        
        redirect = str(project.getDebugProperty("REDIRECT"))
        noencoding = \
            project.getDebugProperty("NOENCODING") and '--no-encoding' or ''
        
        if project.getDebugProperty("REMOTEDEBUGGER"):
            ipaddr = socket.gethostbyname(socket.gethostname())
            rexec = project.getDebugProperty("REMOTECOMMAND")
            rhost = project.getDebugProperty("REMOTEHOST")
            if rhost == "":
                rhost = "localhost"
            if rexec:
                args = Utilities.parseOptionString(rexec) + \
                       [rhost, interpreter, os.path.abspath(debugClient),
                        noencoding, `self.port()`, redirect, ipaddr]
                args[0] = Utilities.getExecutablePath(args[0])
                self.clientPID = os.spawnv(os.P_NOWAIT, args[0], args)
                # set translation function
                if project.getDebugProperty("PATHTRANSLATION"):
                    self.translateRemote = project.getDebugProperty("REMOTEPATH")
                    self.translateLocal = project.getDebugProperty("LOCALPATH")
                    self.translate = self.__remoteTranslation
                else:
                    self.translate = self.__identityTranslation
                return
        
        # set translation function
        self.translate = self.__identityTranslation
        
        # setup the environment for the debugger
        if project.getDebugProperty("ENVIRONMENTOVERRIDE"):
            clientEnv = {}
        else:
            clientEnv = os.environ.copy()
        envlist = Utilities.parseEnvironmentString(\
            project.getDebugProperty("ENVIRONMENTSTRING"))
        for el in envlist:
            try:
                key, value = el.split('=', 1)
                if value.startswith('"') or value.startswith("'"):
                    value = value[1:-1]
                clientEnv[unicode(key)] = unicode(value)
            except UnpackError:
                pass
        
        if project.getDebugProperty("CONSOLEDEBUGGER"):
            ccmd = unicode(project.getDebugProperty("CONSOLECOMMAND"))
            if ccmd:
                args = Utilities.parseOptionString(ccmd) + \
                       [interpreter, os.path.abspath(debugClient),
                        noencoding, `self.port()`, '0']
                args[0] = Utilities.getExecutablePath(args[0])
                try:
                    self.clientPID = os.spawnve(os.P_NOWAIT, args[0], args, clientEnv)
                except OSError:
                    pass
                return
        
        try:
            if sys.platform == "win32":
                self.clientPID = os.spawnve(os.P_NOWAIT,interpreter,
                    ['"%s"' % interpreter, '"%s"' % debugClient, 
                     noencoding, `self.port()`, redirect], clientEnv)
            else:
                self.clientPID = os.spawnve(os.P_NOWAIT,interpreter,
                    [interpreter, debugClient, 
                     noencoding, `self.port()`, redirect], clientEnv)
        except OSError:
            pass

    def handleClientCapabilities(self, cap, clType):
        """
        Private slot to handle the reporting of the clients capabilities.
        
        @param cap client capabilities (integer)
        @param clType type of the debug client (string)
        """
        self.clientCapabilities[clType] = cap
        
    def getClientCapabilities(self, type):
        """
        Public method to retrieve the debug clients capabilities.
        
        @param type debug client type (string)
        @return debug client capabilities (integer)
        """
        try:
            return self.clientCapabilities[type]
        except KeyError:
            return 0    # no capabilities
        
    def newConnection(self,sockfd):
        """
        Reimplemented to handle a new connection.
        
        @param sockfd the socket descriptor
        """
        sock = QSocket()
        sock.setSocket(sockfd)

        # If we already have a connection, refuse this one.  It will be closed
        # automatically.
        if self.qsock is not None:
            return

        self.connect(sock,SIGNAL('readyRead()'),self.handleLine)
        self.connect(sock,SIGNAL('connectionClosed()'),self.startClient)

        self.qsock = sock

        # Get the remote clients capabilities
        self.remoteCapabilities()
        
        # Perform actions neccessary, if client type has changed
        if self.lastClientType != self.clientType:
            self.lastClientType = self.clientType
            self.remoteBanner()
        
        # Send commands that were waiting for the connection.
        for cmd in self.queue:
            self.qsock.writeBlock(str(cmd))

        self.queue = []

    def shutdownServer(self):
        """
        Public method to cleanly shut down.
        
        It closes our socket and shuts down
        the debug client. (Needed on Win OS)
        """
        if self.qsock is None:
            return
            
        # do not want any slots called during shutdown
        self.disconnect(self.qsock, SIGNAL('connectionClosed()'), self.startClient)
        self.disconnect(self.qsock, SIGNAL('readyRead()'), self.handleLine)
        
        # close down socket, and shut down client as well.
        self.sendCommand('%s\n' % RequestShutdown)
        self.qsock.flush()
        
        self.qsock.close()
        
        # reinitialize
        self.qsock = None
        self.queue = []

    def remoteEnvironment(self, env):
        """
        Public method to set the environment for a program to debug, run, ...
        
        @param env environment settings (string)
        """
        envlist = Utilities.parseEnvironmentString(env)
        envdict = {}
        for el in envlist:
            try:
                key, value = el.split('=', 1)
                if value.startswith('"') or value.startswith("'"):
                    value = value[1:-1]
                envdict[unicode(key)] = unicode(value)
            except UnpackError:
                pass
        self.sendCommand('%s%s\n' % (RequestEnv, str(envdict)))
        
    def remoteLoad(self,fn,argv,wd,env,tracePython,forProject=0):
        """
        Public method to load a new program to debug.
        
        @param fn the filename to debug (string)
        @param argv the commandline arguments to pass to the program (string or QString)
        @param wd the working directory for the program (string)
        @param env environment settings (string)
        @param tracePython flag indicating if the Python library should be traced
            as well
        @param forProject flag indicating a project related action (boolean)
        """
        # Restart the client if there is already a program loaded.
        try:
            self.setClientType(ClientTypeAssociations[os.path.splitext(fn)[1]])
        except KeyError:
            self.setClientType('Python')    # assume it is a Python file
        self.startClient(0, forProject=forProject)
        
        self.remoteEnvironment(env)
        
        wd = self.translate(wd, 0)
        fn = self.translate(os.path.abspath(unicode(fn)), 0)
        self.sendCommand('%s%s|%s|%s|%d\n' % \
            (RequestLoad, unicode(wd),
             unicode(fn), str(Utilities.parseOptionString(argv)),tracePython))
        self.debugging = 1
        self.restoreBreakpoints()

    def remoteRun(self,fn,argv,wd,env,forProject=0):
        """
        Public method to load a new program to run.
        
        @param fn the filename to run (string)
        @param argv the commandline arguments to pass to the program (string or QString)
        @param wd the working directory for the program (string)
        @param env environment settings (string)
        @param forProject flag indicating a project related action (boolean)
        """
        # Restart the client if there is already a program loaded.
        try:
            self.setClientType(ClientTypeAssociations[os.path.splitext(fn)[1]])
        except KeyError:
            self.setClientType('Python')    # assume it is a Python file
        self.startClient(0, forProject=forProject)
        
        self.remoteEnvironment(env)
        
        wd = self.translate(wd, 0)
        fn = self.translate(os.path.abspath(unicode(fn)), 0)
        self.sendCommand('%s%s|%s|%s\n' % \
            (RequestRun, unicode(wd),
             unicode(fn), str(Utilities.parseOptionString(argv))))
        self.debugging = 0

    def remoteCoverage(self,fn,argv,wd,env,erase,exclude="",forProject=0):
        """
        Public method to load a new program to collect coverage data.
        
        @param fn the filename to run (string)
        @param argv the commandline arguments to pass to the program (string or QString)
        @param wd the working directory for the program (string)
        @param env environment settings (string)
        @param erase flag indicating that coverage info should be cleared first (boolean)
        @param exclude searchpattern marking line to exclude from coverage
        @param forProject flag indicating a project related action (boolean)
        """
        # Restart the client if there is already a program loaded.
        try:
            self.setClientType(ClientTypeAssociations[os.path.splitext(fn)[1]])
        except KeyError:
            self.setClientType('Python')    # assume it is a Python file
        self.startClient(0, forProject=forProject)
        
        self.remoteEnvironment(env)
        
        wd = self.translate(wd, 0)
        fn = self.translate(os.path.abspath(unicode(fn)), 0)
        self.sendCommand('%s%s@@%s@@%s@@%d@@%s\n' % \
            (RequestCoverage, unicode(wd),
             unicode(fn),str(Utilities.parseOptionString(argv)),erase,unicode(exclude)))
        self.debugging = 0

    def remoteProfile(self,fn,argv,wd,env,erase,forProject=0):
        """
        Public method to load a new program to collect profiling data.
        
        @param fn the filename to run (string)
        @param argv the commandline arguments to pass to the program (string or QString)
        @param wd the working directory for the program (string)
        @param env environment settings (string)
        @param erase flag indicating that timing info should be cleared first (boolean)
        @param forProject flag indicating a project related action (boolean)
        """
        # Restart the client if there is already a program loaded.
        try:
            self.setClientType(ClientTypeAssociations[os.path.splitext(fn)[1]])
        except KeyError:
            self.setClientType('Python')    # assume it is a Python file
        self.startClient(0, forProject=forProject)
        
        self.remoteEnvironment(env)
        
        wd = self.translate(wd, 0)
        fn = self.translate(os.path.abspath(unicode(fn)), 0)
        self.sendCommand('%s%s|%s|%s|%d\n' % \
            (RequestProfile, unicode(wd),
             unicode(fn),str(Utilities.parseOptionString(argv)),erase))
        self.debugging = 0

    def remoteCyclops(self,fn,argv,wd,env,modfunc,reports,forProject=0):
        """
        Public method to load a new program to collect profiling data.
        
        @param fn the filename to run (string)
        @param argv the commandline arguments to pass to the program (string or QString)
        @param wd the working directory for the program (string)
        @param env environment settings (string)
        @param modfunc name of a module function which is the main 
            entry point (string)
        @param reports bit mask specifying the reports wanted (integer)
        @param forProject flag indicating a project related action (boolean)
        """
        # Restart the client if there is already a program loaded.
        try:
            self.setClientType(ClientTypeAssociations[os.path.splitext(fn)[1]])
        except KeyError:
            self.setClientType('Python')    # assume it is a Python file
        self.startClient(0, forProject=forProject)
        
        self.remoteEnvironment(env)
        
        wd = self.translate(wd, 0)
        fn = self.translate(os.path.abspath(unicode(fn)), 0)
        self.sendCommand('%s%s|%s|%s|%s|%d\n' % \
            (RequestCyclops, unicode(wd),
             unicode(fn),str(Utilities.parseOptionString(argv)),str(modfunc),reports))
        self.debugging = 0

    def remoteStatement(self,stmt):
        """
        Public method to execute a Python statement.  
        
        @param stmt the Python statement to execute (string). It
              should not have a trailing newline.
        """
        self.sendCommand('%s\n' % stmt)
        self.sendCommand(RequestOK + '\n')

    def remoteStep(self):
        """
        Public method to single step the debugged program.
        """
        self.sendCommand(RequestStep + '\n')

    def remoteStepOver(self):
        """
        Public method to step over the debugged program.
        """
        self.sendCommand(RequestStepOver + '\n')

    def remoteStepOut(self):
        """
        Public method to step out the debugged program.
        """
        self.sendCommand(RequestStepOut + '\n')

    def remoteStepQuit(self):
        """
        Public method to stop the debugged program.
        """
        self.sendCommand(RequestStepQuit + '\n')

    def remoteContinue(self, special=0):
        """
        Public method to continue the debugged program.
        
        @param special flag indicating a special continue operation
        """
        self.sendCommand('%s%d\n' % (RequestContinue, special))

    def remoteBreakpoint(self,fn,line,set,cond=None,temp=0):
        """
        Public method to set or clear a breakpoint.
        
        @param fn filename the breakpoint belongs to (string)
        @param line linenumber of the breakpoint (int)
        @param set flag indicating setting or resetting a breakpoint (boolean)
        @param cond condition of the breakpoint (string)
        @param temp flag indicating a temporary breakpoint (boolean)
        """
        fn = self.translate(fn, 0)
        self.sendCommand('%s%s,%d,%d,%d,%s\n' % (RequestBreak,fn,line,temp,set,cond))
        
    def remoteBreakpointEnable(self,fn,line,enable):
        """
        Public method to enable or disable a breakpoint.
        
        @param fn filename the breakpoint belongs to (string)
        @param line linenumber of the breakpoint (int)
        @param enable flag indicating enabling or disabling a breakpoint (boolean)
        """
        fn = self.translate(fn, 0)
        self.sendCommand('%s%s,%d,%d\n' % (RequestBreakEnable,fn,line,enable))
        
    def remoteBreakpointIgnore(self,fn,line,count):
        """
        Public method to ignore a breakpoint the next couple of occurences.
        
        @param fn filename the breakpoint belongs to (string)
        @param line linenumber of the breakpoint (int)
        @param count number of occurences to ignore (int)
        """
        fn = self.translate(fn, 0)
        self.sendCommand('%s%s,%d,%d\n' % (RequestBreakIgnore,fn,line,count))
        
    def remoteWatchpoint(self,cond,set,temp=0):
        """
        Public method to set or clear a watchpoint.
        
        @param cond condition of the watchpoint (string)
        @param set flag indicating setting or resetting a watchpoint (boolean)
        @param temp flag indicating a temporary watchpoint (boolean)
        """
        self.sendCommand('%s%s,%d,%d\n' % (RequestWatch,cond,temp,set))
    
    def remoteWatchpointEnable(self,cond,enable):
        """
        Public method to enable or disable a watchpoint.
        
        @param cond condition of the watchpoint (string)
        @param enable flag indicating enabling or disabling a watchpoint (boolean)
        """
        self.sendCommand('%s%s,%d\n' % (RequestWatchEnable,cond,enable))
    
    def remoteWatchpointIgnore(self,cond,count):
        """
        Public method to ignore a watchpoint the next couple of occurences.
        
        @param cond condition of the watchpoint (string)
        @param count number of occurences to ignore (int)
        """
        self.sendCommand('%s%s,%d\n' % (RequestWatchIgnore,cond,count))
    
    def remoteRawInput(self,s):
        """
        Public method to send the raw input to the debugged program.
        
        @param s the raw input (string)
        """
        self.sendCommand(s + '\n')
        self.emit(PYSIGNAL('clientRawInputSent'), ())
        
    def remoteClientVariables(self, scope, filter, framenr=0):
        """
        Public method to request the variables of the debugged program.
        
        @param scope the scope of the variables (0 = local, 1 = global)
        @param filter list of variable types to filter out (list of int)
        @param framenr framenumber of the variables to retrieve (int)
        """
        self.sendCommand('%s%d, %d, %s\n' % (RequestVariables,framenr,scope,str(filter)))
        
    def remoteClientVariable(self, scope, filter, var, framenr=0):
        """
        Public method to request the variables of the debugged program.
        
        @param scope the scope of the variables (0 = local, 1 = global)
        @param filter list of variable types to filter out (list of int)
        @param var list encoded name of variable to retrieve (string)
        @param framenr framenumber of the variables to retrieve (int)
        """
        self.sendCommand('%s%s, %d, %d, %s\n' % (RequestVariable,str(var),framenr,scope,str(filter)))
        
    def remoteClientSetFilter(self, scope, filter):
        """
        Public method to set a variables filter list.
        
        @param scope the scope of the variables (0 = local, 1 = global)
        @param filter regexp string for variable names to filter out (string)
        """
        self.sendCommand('%s%d, "%s"\n' % (RequestSetFilter, scope, filter))
        
    def remoteEval(self, arg):
        """
        Public method to evaluate arg in the current context of the debugged program.
        
        @param arg the arguments to evaluate (string)
        """
        self.sendCommand('%s%s\n' % (RequestEval,arg))
        
    def remoteExec(self, stmt):
        """
        Public method to execute stmt in the current context of the debugged program.
        
        @param stmt statement to execute (string)
        """
        self.sendCommand('%s%s\n' % (RequestExec,stmt))
        
    def remoteBanner(self):
        """
        Public slot to get the banner info of the remote client.
        """
        self.sendCommand(RequestBanner + '\n')
        
    def remoteCapabilities(self):
        """
        Public slot to get the debug clients capabilities.
        """
        self.sendCommand(RequestCapabilities + '\n')
        
    def remoteCompletion(self, text):
        """
        Public slot to get the a list of possible commandline completions
        from the remote client.
        
        @param text the text to be completed (string or QString)
        """
        self.sendCommand("%s%s\n" % (RequestCompletion, text))

    def remoteUTPrepare(self,fn,tn,cov,covname,coverase):
        """
        Public method to prepare a new unittest run.
        
        @param fn the filename to load (string)
        @param tn the testname to load (string)
        @param cov flag indicating collection of coverage data is requested
        @param covname filename to be used to assemble the coverage caches
                filename
        @param coverase flag indicating erasure of coverage data is requested
        """
        # Restart the client if there is already a program loaded.
        try:
            self.setClientType(ClientTypeAssociations[os.path.splitext(fn)[1]])
        except KeyError:
            self.setClientType('Python')    # assume it is a Python file
        self.startClient(0)
        
        fn = self.translate(os.path.abspath(unicode(fn)), 0)
        self.sendCommand('%s%s|%s|%d|%s|%d\n' % \
            (RequestUTPrepare,
             unicode(fn), str(tn), cov, str(covname), coverase))
        self.debugging = 0
        
    def remoteUTRun(self):
        """
        Public method to start a unittest run.
        """
        self.sendCommand('%s\n' % RequestUTRun)
        
    def remoteUTStop(self):
        """
        public method to stop a unittest run.
        """
        self.sendCommand('%s\n' % RequestUTStop)
        
    def handleLine(self):
        """
        Private method to handle data from the client.
        """
        while self.qsock and self.qsock.canReadLine():
            qs = self.qsock.readLine()
            if self.codec is not None:
                us = self.codec.fromUnicode(qs)
            else:
                us = qs
            line = str(us)
            if line.endswith(EOT):
                line = line[:-len(EOT)]
                if not line:
                    continue

##            print "Server: ", line          ##debug
            
            eoc = line.find('<') + 1
            
            # Deal with case where user has written directly to stdout
            # or stderr, but not line terminated and we stepped over the
            # write call, in that case the >line< will not be the first
            # string read from the socket...
            boc = line.find('>')
            if boc > 0 and eoc > boc:
                self.emit(PYSIGNAL('clientOutput'),(line[:boc],))
                line = line[boc:]
                eoc = line.find('<') + 1
                boc = line.find('>')
                
            if boc >= 0 and eoc > boc:
                # Emit the signal corresponding to the response.
                resp = line[boc:eoc]

                if resp == ResponseLine:
                    stack = eval(line[eoc:-1])
                    for s in stack:
                        s[0] = self.translate(s[0], 1)
                    cf = stack[0]
                    self.emit(PYSIGNAL('clientLine'),(cf[0],int(cf[1])))
                    self.emit(PYSIGNAL('clientStack'),(stack,))
                    continue

                if resp == ResponseVariables:
                    self.emit(PYSIGNAL('clientVariables'),(line[eoc:-1],))
                    continue

                if resp == ResponseVariable:
                    self.emit(PYSIGNAL('clientVariable'),(line[eoc:-1],))
                    continue

                if resp == ResponseOK:
                    self.emit(PYSIGNAL('clientStatement'),(0,))
                    continue

                if resp == ResponseContinue:
                    self.emit(PYSIGNAL('clientStatement'),(1,))
                    continue

                if resp == ResponseException:
                    exc = line[eoc:-1]
                    exc = self.translate(exc, 1)
                    self.emit(PYSIGNAL('clientException'),(exc,))
                    continue

                if resp == ResponseSyntax:
                    exc = line[eoc:-1]
                    exc = self.translate(exc, 1)
                    self.emit(PYSIGNAL('clientSyntaxError'),(exc,))
                    continue
                    
                if resp == ResponseExit:
                    self.emit(PYSIGNAL('clientExit'),(line[eoc:-1],))
                    if self.passive:
                        self.passiveShutDown()
                    if Preferences.getDebugger("AutomaticReset"):
                        self.startClient(0)
                    continue
                
                if resp == ResponseClearBreak:
                    fn, lineno = line[eoc:-1].split(',')
                    lineno = int(lineno)
                    fn = self.translate(fn, 1)
                    self.emit(PYSIGNAL('clientClearBreak'),(fn, lineno))
                    continue
                    
                if resp == ResponseBPConditionError:
                    fn, lineno = line[eoc:-1].split(',')
                    lineno = int(lineno)
                    fn = self.translate(fn, 1)
                    self.emit(PYSIGNAL('clientBreakConditionError'),(fn, lineno))
                    continue
                    
                if resp == ResponseClearWatch:
                    cond = line[eoc:-1]
                    self.emit(PYSIGNAL('clientClearWatch'),(cond,))
                    continue
                    
                if resp == ResponseWPConditionError:
                    cond = line[eoc:-1]
                    self.emit(PYSIGNAL('clientWatchConditionError'),(cond,))
                    continue
                    
                if resp == ResponseRaw:
                    prompt, echo = eval(line[eoc:-1])
                    self.emit(PYSIGNAL('clientRawInput'),(prompt, echo))
                    continue
                    
                if resp == ResponseBanner:
                    bi = eval(line[eoc:-1])
                    self.emit(PYSIGNAL('clientBanner'),bi)
                    continue
                    
                if resp == ResponseCapabilities:
                    cap = eval(line[eoc:-1])
                    self.emit(PYSIGNAL('clientCapabilities'),cap)
                    continue
                    
                if resp == ResponseCompletion:
                    clstring, text = line[eoc:-1].split('||')
                    cl = eval(clstring)
                    self.emit(PYSIGNAL('clientCompletionList'),(cl,text))
                    continue
                    
                if resp == PassiveStartup:
                    fn, exc = line[eoc:-1].split('|')
                    exc = int(exc)
                    fn = self.translate(fn, 1)
                    self.passiveStartUp(fn, exc)
                    continue
                    
                if resp == ResponseUTPrepared:
                    res, exc_type, exc_value = eval(line[eoc:-1])
                    self.emit(PYSIGNAL('utPrepared'),(res, exc_type, exc_value))
                    continue
                    
                if resp == ResponseUTStartTest:
                    testinfo = eval(line[eoc:-1])
                    self.emit(PYSIGNAL('utStartTest'),testinfo)
                    continue
                    
                if resp == ResponseUTStopTest:
                    self.emit(PYSIGNAL('utStopTest'),())
                    continue
                    
                if resp == ResponseUTTestFailed:
                    err = eval(line[eoc:-1])
                    self.emit(PYSIGNAL('utTestFailed'),err)
                    continue
                    
                if resp == ResponseUTTestErrored:
                    err = eval(line[eoc:-1])
                    self.emit(PYSIGNAL('utTestErrored'),err)
                    continue
                    
                if resp == ResponseUTFinished:
                    self.emit(PYSIGNAL('utFinished'),())
                    continue
                    
                if resp == ResponseCyclopsError:
                    fn, modfunc = eval(line[eoc:-1])
                    fn = self.translate(fn, 1)
                    self.emit(PYSIGNAL('cyclopsError'), (fn, modfunc))
                    continue

            self.emit(PYSIGNAL('clientOutput'),(line,))

    def passiveStartUp(self, fn, exc):
        """
        Private method to handle a passive debug connection.
        
        @param fn filename of the debugged script (string)
        @param exc flag to enable exception reporting of the IDE (boolean)
        """
        print unicode(self.trUtf8("Passive debug connection received"))
        self.passiveClientExited = 0
        self.restoreBreakpoints()
        self.emit(PYSIGNAL('passiveDebugStarted'), (fn, exc))
        
    def passiveShutDown(self):
        """
        Private method to shut down a passive debug connection.
        """
        self.passiveClientExited = 1
        self.shutdownServer()
        print unicode(self.trUtf8("Passive debug connection closed"))
        
    def setClientType(self, clType):
        """
        Private method to set the client type.
        
        @param clType type of client to be started (string, Python or Ruby)
        """
        if clType is not None and clType in ["Python", "Ruby"]:
            self.clientType = clType
            ok = Preferences.Prefs.settings.writeEntry('/eric3/DebugClient/Type', 
                self.clientType)
        
    def startClient(self,unplanned=1,clType=None,forProject=0):
        """
        Public method to start a debug client.
        
        @param unplanned flag indicating that the client has died
        @param clType type of client to be started (string, Python or Ruby)
        @param forProject flag indicating a project related action (boolean)
        """
        if not self.passive or not self.passiveClientExited: 
            if self.qsock is not None:
                self.shutdownServer()
                self.emit(PYSIGNAL('clientGone'),(unplanned & self.debugging,))
            
        self.setClientType(clType)
        self.startRemote(forProject)

    def sendCommand(self,cmd):
        """
        Private method to send a single line command to the client.
        
        @param cmd command to send to the debug client (string)
        """
        if self.qsock is not None:
            self.qsock.writeBlock(str(cmd))
        else:
            self.queue.append(cmd)

    def restoreBreakpoints(self):
        """
        Private method to restore the break points and watch points after a restart.
        """
        vm = qApp.mainWidget().getViewManager()
        for fn in vm.getOpenFilenames():
            editor = vm.getOpenEditor(fn)
            breaks = editor.getBreakpoints()
            for line, cond, temp, enabled, ignorecount in breaks:
                self.remoteBreakpoint(fn, line, 1, cond, temp)
                if not enabled:
                    self.remoteBreakpointEnable(fn, line, 0)
                if ignorecount:
                    self.remoteBreakpointIgnore(fn, line, ignorecount)
        
        breaks = self.watchpointViewer.getWatchpoints()
        for cond, temp, enabled, ignorecount in breaks:
            self.remoteWatchpoint(cond, 1, temp)
            if not enabled:
                self.remoteWatchpointEnable(cond, 0)
            if ignorecount:
                self.remoteWatchpointIgnore(cond, ignorecount)
    
    def setWatchpointViewer(self, watchpointViewer):
        """
        Public method to set the watchpoint viewer.
        
        @param reference to the watchpoint viewer widget
        """
        self.watchpointViewer = watchpointViewer
    
    def getWatchpointViewer(self):
        """
        Public method to get the watchpoint viewer.
        
        @return reference to the watchpoint viewer widget
        """
        return self.watchpointViewer
