1from _pydevd_bundle.pydevd_constants import DebugInfoHolder, IS_PY2, \
2    get_global_debugger, GetGlobalDebugger, set_global_debugger  # Keep for backward compatibility @UnusedImport
3from _pydevd_bundle.pydevd_utils import quote_smart as quote, to_string
4from _pydevd_bundle.pydevd_comm_constants import ID_TO_MEANING, CMD_EXIT
5from _pydevd_bundle.pydevd_constants import HTTP_PROTOCOL, HTTP_JSON_PROTOCOL, \
6    get_protocol, IS_JYTHON, ForkSafeLock
7import json
8from _pydev_bundle import pydev_log
9
10
11class _BaseNetCommand(object):
12
13    # Command id. Should be set in instance.
14    id = -1
15
16    # Dict representation of the command to be set in instance. Only set for json commands.
17    as_dict = None
18
19    def send(self, *args, **kwargs):
20        pass
21
22
23class _NullNetCommand(_BaseNetCommand):
24    pass
25
26
27class _NullExitCommand(_NullNetCommand):
28
29    id = CMD_EXIT
30
31
32# Constant meant to be passed to the writer when the command is meant to be ignored.
33NULL_NET_COMMAND = _NullNetCommand()
34
35# Exit command -- only internal (we don't want/need to send this to the IDE).
36NULL_EXIT_COMMAND = _NullExitCommand()
37
38
39class NetCommand(_BaseNetCommand):
40    """
41    Commands received/sent over the network.
42
43    Command can represent command received from the debugger,
44    or one to be sent by daemon.
45    """
46    next_seq = 0  # sequence numbers
47
48    _showing_debug_info = 0
49    _show_debug_info_lock = ForkSafeLock(rlock=True)
50
51    def __init__(self, cmd_id, seq, text, is_json=False):
52        """
53        If sequence is 0, new sequence will be generated (otherwise, this was the response
54        to a command from the client).
55        """
56        protocol = get_protocol()
57        self.id = cmd_id
58        if seq == 0:
59            NetCommand.next_seq += 2
60            seq = NetCommand.next_seq
61
62        self.seq = seq
63
64        if is_json:
65            if hasattr(text, 'to_dict'):
66                as_dict = text.to_dict(update_ids_to_dap=True)
67            else:
68                assert isinstance(text, dict)
69                as_dict = text
70            as_dict['pydevd_cmd_id'] = cmd_id
71            as_dict['seq'] = seq
72            self.as_dict = as_dict
73            text = json.dumps(as_dict)
74
75        if IS_PY2:
76            if isinstance(text, unicode):
77                text = text.encode('utf-8')
78            else:
79                assert isinstance(text, str)
80        else:
81            assert isinstance(text, str)
82
83        if DebugInfoHolder.DEBUG_TRACE_LEVEL >= 1:
84            self._show_debug_info(cmd_id, seq, text)
85
86        if is_json:
87            msg = text
88        else:
89            if protocol not in (HTTP_PROTOCOL, HTTP_JSON_PROTOCOL):
90                encoded = quote(to_string(text), '/<>_=" \t')
91                msg = '%s\t%s\t%s\n' % (cmd_id, seq, encoded)
92
93            else:
94                msg = '%s\t%s\t%s' % (cmd_id, seq, text)
95
96        if IS_PY2:
97            assert isinstance(msg, str)  # i.e.: bytes
98            as_bytes = msg
99        else:
100            if isinstance(msg, str):
101                msg = msg.encode('utf-8')
102
103            assert isinstance(msg, bytes)
104            as_bytes = msg
105        self._as_bytes = as_bytes
106
107    def send(self, sock):
108        as_bytes = self._as_bytes
109        try:
110            if get_protocol() in (HTTP_PROTOCOL, HTTP_JSON_PROTOCOL):
111                sock.sendall(('Content-Length: %s\r\n\r\n' % len(as_bytes)).encode('ascii'))
112            sock.sendall(as_bytes)
113        except:
114            if IS_JYTHON:
115                # Ignore errors in sock.sendall in Jython (seems to be common for Jython to
116                # give spurious exceptions at interpreter shutdown here).
117                pass
118            else:
119                raise
120
121    @classmethod
122    def _show_debug_info(cls, cmd_id, seq, text):
123        with cls._show_debug_info_lock:
124            # Only one thread each time (rlock).
125            if cls._showing_debug_info:
126                # avoid recursing in the same thread (just printing could create
127                # a new command when redirecting output).
128                return
129
130            cls._showing_debug_info += 1
131            try:
132                out_message = 'sending cmd (%s) --> ' % (get_protocol(),)
133                out_message += "%20s" % ID_TO_MEANING.get(str(cmd_id), 'UNKNOWN')
134                out_message += ' '
135                out_message += text.replace('\n', ' ')
136                try:
137                    pydev_log.critical('%s\n', out_message)
138                except:
139                    pass
140            finally:
141                cls._showing_debug_info -= 1
142
143