diff --git a/script.module.web-pdb/addon.xml b/script.module.web-pdb/addon.xml
index bc3ab42e97..97726dade0 100644
--- a/script.module.web-pdb/addon.xml
+++ b/script.module.web-pdb/addon.xml
@@ -1,11 +1,11 @@
-
+
executable
@@ -22,6 +22,7 @@
icon.png
resources/screenshot.jpg
- - 1.5.6: Fixed not being able to assign a local variable via the debugger console.
+ - 1.6.2: Various internal changes.
+- 1.6.0: Dropped Python 2 compatibility.
diff --git a/script.module.web-pdb/libs/web_pdb/__init__.py b/script.module.web-pdb/libs/web_pdb/__init__.py
index 8aa807db23..8088bfea41 100644
--- a/script.module.web-pdb/libs/web_pdb/__init__.py
+++ b/script.module.web-pdb/libs/web_pdb/__init__.py
@@ -1,6 +1,5 @@
-# coding: utf-8
# Author: Roman Miroshnychenko aka Roman V.M.
-# E-mail: romanvm@yandex.ua
+# E-mail: roman1972@gmail.com
#
# Copyright (c) 2016 Roman Miroshnychenko
#
@@ -25,20 +24,18 @@
A web-interface for Python's built-in PDB debugger
"""
-from __future__ import absolute_import, unicode_literals
import inspect
import os
+import random
import sys
import traceback
-import random
from contextlib import contextmanager
+from pdb import Pdb
from pprint import pformat
-if sys.version_info[0] == 2:
- from .pdb_py2 import PdbPy2 as Pdb
-else:
- from pdb import Pdb
+
import xbmc
from xbmcgui import Dialog
+
from .web_console import WebConsole
__all__ = ['WebPdb', 'set_trace', 'post_mortem', 'catch_post_mortem']
@@ -68,7 +65,7 @@ def __init__(self, host='', port=5555):
random.seed()
port = random.randint(32768, 65536)
self.console = WebConsole(host, port, self)
- Pdb.__init__(self, stdin=self.console, stdout=self.console)
+ super().__init__(stdin=self.console, stdout=self.console)
WebPdb.active_instance = self
def do_quit(self, arg):
@@ -80,7 +77,7 @@ def do_quit(self, arg):
self.console.flush()
self.console.close()
WebPdb.active_instance = None
- return Pdb.do_quit(self, arg)
+ return super().do_quit(arg)
do_q = do_exit = do_quit
@@ -96,18 +93,13 @@ def do_inspect(self, arg):
else:
obj = WebPdb.null
if obj is not WebPdb.null:
- self.console.writeline(
- '{0} = {1}:\n'.format(arg, type(obj))
- )
+ self.console.writeline(f'{arg} = {type(obj)}:\n')
for name, value in inspect.getmembers(obj):
if not (name.startswith('__') and (name.endswith('__'))):
- self.console.writeline(' {0}: {1}\n'.format(
- name, self._get_repr(value, pretty=True, indent=8)
- ))
+ repr_value = self._get_repr(value, pretty=True, indent=8)
+ self.console.writeline(f' {name}: {repr_value}\n')
else:
- self.console.writeline(
- 'NameError: name "{0}" is not defined\n'.format(arg)
- )
+ self.console.writeline(f'NameError: name "{arg}" is not defined\n')
self.console.flush()
do_i = do_inspect
@@ -130,12 +122,6 @@ def _get_repr(obj, pretty=False, indent=1):
repr_value = pformat(obj, indent)
else:
repr_value = repr(obj)
- if sys.version_info[0] == 2:
- # Try to convert Unicode string to human-readable form
- try:
- repr_value = repr_value.decode('raw_unicode_escape')
- except UnicodeError:
- repr_value = repr_value.decode('utf-8', 'replace')
return repr_value
def set_continue(self):
@@ -144,7 +130,7 @@ def set_continue(self):
def dispatch_return(self, frame, arg):
# The parent's method needs to be called first.
- ret = Pdb.dispatch_return(self, frame, arg)
+ ret = super().dispatch_return(frame, arg)
if frame.f_back is None:
self.console.writeline('*** Thread finished ***\n')
if not self.console.closed:
@@ -164,8 +150,6 @@ def get_current_frame_data(self):
"""
filename = self.curframe.f_code.co_filename
lines, start_line = inspect.findsource(self.curframe)
- if sys.version_info[0] == 2:
- lines = [line.decode('utf-8') for line in lines]
return {
'dirname': os.path.dirname(os.path.abspath(filename)) + os.path.sep,
'filename': os.path.basename(filename),
@@ -187,7 +171,7 @@ def _format_variables(self, raw_vars):
for var, value in raw_vars.items():
if not (var.startswith('__') and var.endswith('__')):
repr_value = self._get_repr(value)
- f_vars.append('{0} = {1}'.format(var, repr_value))
+ f_vars.append(f'{var} = {repr_value}')
return '\n'.join(sorted(f_vars))
def get_globals(self):
@@ -328,9 +312,9 @@ def catch_post_mortem(host='', port=5555):
try:
yield
except Exception:
- xbmc.log('Web-PDB: unhandled exception detected:\n{0}'.format(
- traceback.format_exc()), xbmc.LOGERROR
- )
+ stack_trace = traceback.format_exc()
+ xbmc.log(f'Web-PDB: unhandled exception detected:\n{stack_trace}',
+ xbmc.LOGERROR)
xbmc.log('Web-PDB: starting post-mortem debugging...', xbmc.LOGERROR)
Dialog().notification('Web-PDB',
'Addon error! Starting post-mortem debugging.',
diff --git a/script.module.web-pdb/libs/web_pdb/asyncore_wsgi/SimpleWebSocketServer.py b/script.module.web-pdb/libs/web_pdb/asyncore_wsgi/SimpleWebSocketServer.py
index 041e17101b..3970e0f203 100644
--- a/script.module.web-pdb/libs/web_pdb/asyncore_wsgi/SimpleWebSocketServer.py
+++ b/script.module.web-pdb/libs/web_pdb/asyncore_wsgi/SimpleWebSocketServer.py
@@ -10,24 +10,17 @@
Asynchronous WebSocket handler
"""
-from __future__ import absolute_import
-import sys
-VER = sys.version_info[0]
-if VER >= 3:
- from http.server import BaseHTTPRequestHandler
- from io import StringIO, BytesIO
-else:
- from BaseHTTPServer import BaseHTTPRequestHandler
- from StringIO import StringIO
-
-import asyncore
-import hashlib
import base64
+import codecs
+import errno
+import hashlib
import socket
import struct
-import errno
-import codecs
from collections import deque
+from http.server import BaseHTTPRequestHandler
+from io import BytesIO
+
+from . import asyncore
from .. import logging
__all__ = ['WebSocket', 'AsyncWebSocketHandler']
@@ -36,10 +29,7 @@
def _check_unicode(val):
- if VER >= 3:
- return isinstance(val, str)
- else:
- return isinstance(val, unicode)
+ return isinstance(val, str)
class WebSocketError(Exception):
@@ -47,11 +37,9 @@ class WebSocketError(Exception):
class HTTPRequest(BaseHTTPRequestHandler):
+
def __init__(self, request_text):
- if VER >= 3:
- self.rfile = BytesIO(request_text)
- else:
- self.rfile = StringIO(request_text)
+ self.rfile = BytesIO(request_text)
self.raw_requestline = self.rfile.readline()
self.error_code = self.error_message = None
self.parse_request()
@@ -290,12 +278,8 @@ def _handleData(self):
if not data:
raise WebSocketError("remote socket closed")
- if VER >= 3:
- for d in data:
- self._parseMessage(d)
- else:
- for d in data:
- self._parseMessage(ord(d))
+ for d in data:
+ self._parseMessage(d)
def close(self, status=1000, reason=u''):
"""
diff --git a/script.module.web-pdb/libs/web_pdb/asyncore_wsgi/__init__.py b/script.module.web-pdb/libs/web_pdb/asyncore_wsgi/__init__.py
index 7710c9cdd6..5a406645a8 100644
--- a/script.module.web-pdb/libs/web_pdb/asyncore_wsgi/__init__.py
+++ b/script.module.web-pdb/libs/web_pdb/asyncore_wsgi/__init__.py
@@ -31,8 +31,6 @@ def handleClose(self):
the Standard Library and the echo WebSocket on ``'/ws'`` path.
"""
-from __future__ import absolute_import
-import asyncore
import select
import socket
from errno import EINTR
@@ -40,6 +38,8 @@ def handleClose(self):
from shutil import copyfileobj
from tempfile import TemporaryFile
from wsgiref.simple_server import WSGIServer, ServerHandler, WSGIRequestHandler
+
+from . import asyncore
from .SimpleWebSocketServer import AsyncWebSocketHandler
from .. import logging
diff --git a/script.module.web-pdb/libs/web_pdb/asyncore_wsgi/asyncore.py b/script.module.web-pdb/libs/web_pdb/asyncore_wsgi/asyncore.py
new file mode 100644
index 0000000000..828f4d4fe7
--- /dev/null
+++ b/script.module.web-pdb/libs/web_pdb/asyncore_wsgi/asyncore.py
@@ -0,0 +1,644 @@
+# -*- Mode: Python -*-
+# Id: asyncore.py,v 2.51 2000/09/07 22:29:26 rushing Exp
+# Author: Sam Rushing
+
+# ======================================================================
+# Copyright 1996 by Sam Rushing
+#
+# All Rights Reserved
+#
+# Permission to use, copy, modify, and distribute this software and
+# its documentation for any purpose and without fee is hereby
+# granted, provided that the above copyright notice appear in all
+# copies and that both that copyright notice and this permission
+# notice appear in supporting documentation, and that the name of Sam
+# Rushing not be used in advertising or publicity pertaining to
+# distribution of the software without specific, written prior
+# permission.
+#
+# SAM RUSHING DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+# INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
+# NO EVENT SHALL SAM RUSHING BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+# OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+# ======================================================================
+
+"""Basic infrastructure for asynchronous socket service clients and servers.
+
+There are only two ways to have a program on a single processor do "more
+than one thing at a time". Multi-threaded programming is the simplest and
+most popular way to do it, but there is another very different technique,
+that lets you have nearly all the advantages of multi-threading, without
+actually using multiple threads. it's really only practical if your program
+is largely I/O bound. If your program is CPU bound, then pre-emptive
+scheduled threads are probably what you really need. Network servers are
+rarely CPU-bound, however.
+
+If your operating system supports the select() system call in its I/O
+library (and nearly all do), then you can use it to juggle multiple
+communication channels at once; doing other work while your I/O is taking
+place in the "background." Although this strategy can seem strange and
+complex, especially at first, it is in many ways easier to understand and
+control than multi-threaded programming. The module documented here solves
+many of the difficult problems for you, making the task of building
+sophisticated high-performance network servers and clients a snap.
+"""
+
+import select
+import socket
+import sys
+import time
+import warnings
+
+import os
+from errno import EALREADY, EINPROGRESS, EWOULDBLOCK, ECONNRESET, EINVAL, \
+ ENOTCONN, ESHUTDOWN, EISCONN, EBADF, ECONNABORTED, EPIPE, EAGAIN, \
+ errorcode
+
+_DISCONNECTED = frozenset({ECONNRESET, ENOTCONN, ESHUTDOWN, ECONNABORTED, EPIPE,
+ EBADF})
+
+try:
+ socket_map
+except NameError:
+ socket_map = {}
+
+def _strerror(err):
+ try:
+ return os.strerror(err)
+ except (ValueError, OverflowError, NameError):
+ if err in errorcode:
+ return errorcode[err]
+ return "Unknown error %s" %err
+
+class ExitNow(Exception):
+ pass
+
+_reraised_exceptions = (ExitNow, KeyboardInterrupt, SystemExit)
+
+def read(obj):
+ try:
+ obj.handle_read_event()
+ except _reraised_exceptions:
+ raise
+ except:
+ obj.handle_error()
+
+def write(obj):
+ try:
+ obj.handle_write_event()
+ except _reraised_exceptions:
+ raise
+ except:
+ obj.handle_error()
+
+def _exception(obj):
+ try:
+ obj.handle_expt_event()
+ except _reraised_exceptions:
+ raise
+ except:
+ obj.handle_error()
+
+def readwrite(obj, flags):
+ try:
+ if flags & select.POLLIN:
+ obj.handle_read_event()
+ if flags & select.POLLOUT:
+ obj.handle_write_event()
+ if flags & select.POLLPRI:
+ obj.handle_expt_event()
+ if flags & (select.POLLHUP | select.POLLERR | select.POLLNVAL):
+ obj.handle_close()
+ except OSError as e:
+ if e.args[0] not in _DISCONNECTED:
+ obj.handle_error()
+ else:
+ obj.handle_close()
+ except _reraised_exceptions:
+ raise
+ except:
+ obj.handle_error()
+
+def poll(timeout=0.0, map=None):
+ if map is None:
+ map = socket_map
+ if map:
+ r = []; w = []; e = []
+ for fd, obj in list(map.items()):
+ is_r = obj.readable()
+ is_w = obj.writable()
+ if is_r:
+ r.append(fd)
+ # accepting sockets should not be writable
+ if is_w and not obj.accepting:
+ w.append(fd)
+ if is_r or is_w:
+ e.append(fd)
+ if [] == r == w == e:
+ time.sleep(timeout)
+ return
+
+ r, w, e = select.select(r, w, e, timeout)
+
+ for fd in r:
+ obj = map.get(fd)
+ if obj is None:
+ continue
+ read(obj)
+
+ for fd in w:
+ obj = map.get(fd)
+ if obj is None:
+ continue
+ write(obj)
+
+ for fd in e:
+ obj = map.get(fd)
+ if obj is None:
+ continue
+ _exception(obj)
+
+def poll2(timeout=0.0, map=None):
+ # Use the poll() support added to the select module in Python 2.0
+ if map is None:
+ map = socket_map
+ if timeout is not None:
+ # timeout is in milliseconds
+ timeout = int(timeout*1000)
+ pollster = select.poll()
+ if map:
+ for fd, obj in list(map.items()):
+ flags = 0
+ if obj.readable():
+ flags |= select.POLLIN | select.POLLPRI
+ # accepting sockets should not be writable
+ if obj.writable() and not obj.accepting:
+ flags |= select.POLLOUT
+ if flags:
+ pollster.register(fd, flags)
+
+ r = pollster.poll(timeout)
+ for fd, flags in r:
+ obj = map.get(fd)
+ if obj is None:
+ continue
+ readwrite(obj, flags)
+
+poll3 = poll2 # Alias for backward compatibility
+
+def loop(timeout=30.0, use_poll=False, map=None, count=None):
+ if map is None:
+ map = socket_map
+
+ if use_poll and hasattr(select, 'poll'):
+ poll_fun = poll2
+ else:
+ poll_fun = poll
+
+ if count is None:
+ while map:
+ poll_fun(timeout, map)
+
+ else:
+ while map and count > 0:
+ poll_fun(timeout, map)
+ count = count - 1
+
+class dispatcher:
+
+ debug = False
+ connected = False
+ accepting = False
+ connecting = False
+ closing = False
+ addr = None
+ ignore_log_types = frozenset({'warning'})
+
+ def __init__(self, sock=None, map=None):
+ if map is None:
+ self._map = socket_map
+ else:
+ self._map = map
+
+ self._fileno = None
+
+ if sock:
+ # Set to nonblocking just to make sure for cases where we
+ # get a socket from a blocking source.
+ sock.setblocking(0)
+ self.set_socket(sock, map)
+ self.connected = True
+ # The constructor no longer requires that the socket
+ # passed be connected.
+ try:
+ self.addr = sock.getpeername()
+ except OSError as err:
+ if err.args[0] in (ENOTCONN, EINVAL):
+ # To handle the case where we got an unconnected
+ # socket.
+ self.connected = False
+ else:
+ # The socket is broken in some unknown way, alert
+ # the user and remove it from the map (to prevent
+ # polling of broken sockets).
+ self.del_channel(map)
+ raise
+ else:
+ self.socket = None
+
+ def __repr__(self):
+ status = [self.__class__.__module__+"."+self.__class__.__qualname__]
+ if self.accepting and self.addr:
+ status.append('listening')
+ elif self.connected:
+ status.append('connected')
+ if self.addr is not None:
+ try:
+ status.append('%s:%d' % self.addr)
+ except TypeError:
+ status.append(repr(self.addr))
+ return '<%s at %#x>' % (' '.join(status), id(self))
+
+ __str__ = __repr__
+
+ def add_channel(self, map=None):
+ #self.log_info('adding channel %s' % self)
+ if map is None:
+ map = self._map
+ map[self._fileno] = self
+
+ def del_channel(self, map=None):
+ fd = self._fileno
+ if map is None:
+ map = self._map
+ if fd in map:
+ #self.log_info('closing channel %d:%s' % (fd, self))
+ del map[fd]
+ self._fileno = None
+
+ def create_socket(self, family=socket.AF_INET, type=socket.SOCK_STREAM):
+ self.family_and_type = family, type
+ sock = socket.socket(family, type)
+ sock.setblocking(0)
+ self.set_socket(sock)
+
+ def set_socket(self, sock, map=None):
+ self.socket = sock
+ self._fileno = sock.fileno()
+ self.add_channel(map)
+
+ def set_reuse_addr(self):
+ # try to re-use a server port if possible
+ try:
+ self.socket.setsockopt(
+ socket.SOL_SOCKET, socket.SO_REUSEADDR,
+ self.socket.getsockopt(socket.SOL_SOCKET,
+ socket.SO_REUSEADDR) | 1
+ )
+ except OSError:
+ pass
+
+ # ==================================================
+ # predicates for select()
+ # these are used as filters for the lists of sockets
+ # to pass to select().
+ # ==================================================
+
+ def readable(self):
+ return True
+
+ def writable(self):
+ return True
+
+ # ==================================================
+ # socket object methods.
+ # ==================================================
+
+ def listen(self, num):
+ self.accepting = True
+ if os.name == 'nt' and num > 5:
+ num = 5
+ return self.socket.listen(num)
+
+ def bind(self, addr):
+ self.addr = addr
+ return self.socket.bind(addr)
+
+ def connect(self, address):
+ self.connected = False
+ self.connecting = True
+ err = self.socket.connect_ex(address)
+ if err in (EINPROGRESS, EALREADY, EWOULDBLOCK) \
+ or err == EINVAL and os.name == 'nt':
+ self.addr = address
+ return
+ if err in (0, EISCONN):
+ self.addr = address
+ self.handle_connect_event()
+ else:
+ raise OSError(err, errorcode[err])
+
+ def accept(self):
+ # XXX can return either an address pair or None
+ try:
+ conn, addr = self.socket.accept()
+ except TypeError:
+ return None
+ except OSError as why:
+ if why.args[0] in (EWOULDBLOCK, ECONNABORTED, EAGAIN):
+ return None
+ else:
+ raise
+ else:
+ return conn, addr
+
+ def send(self, data):
+ try:
+ result = self.socket.send(data)
+ return result
+ except OSError as why:
+ if why.args[0] == EWOULDBLOCK:
+ return 0
+ elif why.args[0] in _DISCONNECTED:
+ self.handle_close()
+ return 0
+ else:
+ raise
+
+ def recv(self, buffer_size):
+ try:
+ data = self.socket.recv(buffer_size)
+ if not data:
+ # a closed connection is indicated by signaling
+ # a read condition, and having recv() return 0.
+ self.handle_close()
+ return b''
+ else:
+ return data
+ except OSError as why:
+ # winsock sometimes raises ENOTCONN
+ if why.args[0] in _DISCONNECTED:
+ self.handle_close()
+ return b''
+ else:
+ raise
+
+ def close(self):
+ self.connected = False
+ self.accepting = False
+ self.connecting = False
+ self.del_channel()
+ if self.socket is not None:
+ try:
+ self.socket.close()
+ except OSError as why:
+ if why.args[0] not in (ENOTCONN, EBADF):
+ raise
+
+ # log and log_info may be overridden to provide more sophisticated
+ # logging and warning methods. In general, log is for 'hit' logging
+ # and 'log_info' is for informational, warning and error logging.
+
+ def log(self, message):
+ sys.stderr.write('log: %s\n' % str(message))
+
+ def log_info(self, message, type='info'):
+ if type not in self.ignore_log_types:
+ print('%s: %s' % (type, message))
+
+ def handle_read_event(self):
+ if self.accepting:
+ # accepting sockets are never connected, they "spawn" new
+ # sockets that are connected
+ self.handle_accept()
+ elif not self.connected:
+ if self.connecting:
+ self.handle_connect_event()
+ self.handle_read()
+ else:
+ self.handle_read()
+
+ def handle_connect_event(self):
+ err = self.socket.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR)
+ if err != 0:
+ raise OSError(err, _strerror(err))
+ self.handle_connect()
+ self.connected = True
+ self.connecting = False
+
+ def handle_write_event(self):
+ if self.accepting:
+ # Accepting sockets shouldn't get a write event.
+ # We will pretend it didn't happen.
+ return
+
+ if not self.connected:
+ if self.connecting:
+ self.handle_connect_event()
+ self.handle_write()
+
+ def handle_expt_event(self):
+ # handle_expt_event() is called if there might be an error on the
+ # socket, or if there is OOB data
+ # check for the error condition first
+ err = self.socket.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR)
+ if err != 0:
+ # we can get here when select.select() says that there is an
+ # exceptional condition on the socket
+ # since there is an error, we'll go ahead and close the socket
+ # like we would in a subclassed handle_read() that received no
+ # data
+ self.handle_close()
+ else:
+ self.handle_expt()
+
+ def handle_error(self):
+ nil, t, v, tbinfo = compact_traceback()
+
+ # sometimes a user repr method will crash.
+ try:
+ self_repr = repr(self)
+ except:
+ self_repr = '<__repr__(self) failed for object at %0x>' % id(self)
+
+ self.log_info(
+ 'uncaptured python exception, closing channel %s (%s:%s %s)' % (
+ self_repr,
+ t,
+ v,
+ tbinfo
+ ),
+ 'error'
+ )
+ self.handle_close()
+
+ def handle_expt(self):
+ self.log_info('unhandled incoming priority event', 'warning')
+
+ def handle_read(self):
+ self.log_info('unhandled read event', 'warning')
+
+ def handle_write(self):
+ self.log_info('unhandled write event', 'warning')
+
+ def handle_connect(self):
+ self.log_info('unhandled connect event', 'warning')
+
+ def handle_accept(self):
+ pair = self.accept()
+ if pair is not None:
+ self.handle_accepted(*pair)
+
+ def handle_accepted(self, sock, addr):
+ sock.close()
+ self.log_info('unhandled accepted event', 'warning')
+
+ def handle_close(self):
+ self.log_info('unhandled close event', 'warning')
+ self.close()
+
+# ---------------------------------------------------------------------------
+# adds simple buffered output capability, useful for simple clients.
+# [for more sophisticated usage use asynchat.async_chat]
+# ---------------------------------------------------------------------------
+
+class dispatcher_with_send(dispatcher):
+
+ def __init__(self, sock=None, map=None):
+ dispatcher.__init__(self, sock, map)
+ self.out_buffer = b''
+
+ def initiate_send(self):
+ num_sent = 0
+ num_sent = dispatcher.send(self, self.out_buffer[:65536])
+ self.out_buffer = self.out_buffer[num_sent:]
+
+ def handle_write(self):
+ self.initiate_send()
+
+ def writable(self):
+ return (not self.connected) or len(self.out_buffer)
+
+ def send(self, data):
+ if self.debug:
+ self.log_info('sending %s' % repr(data))
+ self.out_buffer = self.out_buffer + data
+ self.initiate_send()
+
+# ---------------------------------------------------------------------------
+# used for debugging.
+# ---------------------------------------------------------------------------
+
+def compact_traceback():
+ t, v, tb = sys.exc_info()
+ tbinfo = []
+ if not tb: # Must have a traceback
+ raise AssertionError("traceback does not exist")
+ while tb:
+ tbinfo.append((
+ tb.tb_frame.f_code.co_filename,
+ tb.tb_frame.f_code.co_name,
+ str(tb.tb_lineno)
+ ))
+ tb = tb.tb_next
+
+ # just to be safe
+ del tb
+
+ file, function, line = tbinfo[-1]
+ info = ' '.join(['[%s|%s|%s]' % x for x in tbinfo])
+ return (file, function, line), t, v, info
+
+def close_all(map=None, ignore_all=False):
+ if map is None:
+ map = socket_map
+ for x in list(map.values()):
+ try:
+ x.close()
+ except OSError as x:
+ if x.args[0] == EBADF:
+ pass
+ elif not ignore_all:
+ raise
+ except _reraised_exceptions:
+ raise
+ except:
+ if not ignore_all:
+ raise
+ map.clear()
+
+# Asynchronous File I/O:
+#
+# After a little research (reading man pages on various unixen, and
+# digging through the linux kernel), I've determined that select()
+# isn't meant for doing asynchronous file i/o.
+# Heartening, though - reading linux/mm/filemap.c shows that linux
+# supports asynchronous read-ahead. So _MOST_ of the time, the data
+# will be sitting in memory for us already when we go to read it.
+#
+# What other OS's (besides NT) support async file i/o? [VMS?]
+#
+# Regardless, this is useful for pipes, and stdin/stdout...
+
+if os.name == 'posix':
+ class file_wrapper:
+ # Here we override just enough to make a file
+ # look like a socket for the purposes of asyncore.
+ # The passed fd is automatically os.dup()'d
+
+ def __init__(self, fd):
+ self.fd = os.dup(fd)
+
+ def __del__(self):
+ if self.fd >= 0:
+ warnings.warn("unclosed file %r" % self, ResourceWarning,
+ source=self)
+ self.close()
+
+ def recv(self, *args):
+ return os.read(self.fd, *args)
+
+ def send(self, *args):
+ return os.write(self.fd, *args)
+
+ def getsockopt(self, level, optname, buflen=None):
+ if (level == socket.SOL_SOCKET and
+ optname == socket.SO_ERROR and
+ not buflen):
+ return 0
+ raise NotImplementedError("Only asyncore specific behaviour "
+ "implemented.")
+
+ read = recv
+ write = send
+
+ def close(self):
+ if self.fd < 0:
+ return
+ fd = self.fd
+ self.fd = -1
+ os.close(fd)
+
+ def fileno(self):
+ return self.fd
+
+ class file_dispatcher(dispatcher):
+
+ def __init__(self, fd, map=None):
+ dispatcher.__init__(self, None, map)
+ self.connected = True
+ try:
+ fd = fd.fileno()
+ except AttributeError:
+ pass
+ self.set_file(fd)
+ # set it to non-blocking mode
+ os.set_blocking(fd, False)
+
+ def set_file(self, fd):
+ self.socket = file_wrapper(fd)
+ self._fileno = self.socket.fileno()
+ self.add_channel()
diff --git a/script.module.web-pdb/libs/web_pdb/logging.py b/script.module.web-pdb/libs/web_pdb/logging.py
index 9bbc37a354..320bf9c9ed 100644
--- a/script.module.web-pdb/libs/web_pdb/logging.py
+++ b/script.module.web-pdb/libs/web_pdb/logging.py
@@ -1,41 +1,33 @@
-# coding: utf-8
"""Mimics built-in logging module"""
-from __future__ import unicode_literals
-import sys
from traceback import format_exc
import xbmc
__all__ = ['Logger', 'getLogger']
-PY2 = sys.version_info[0] == 2
-
-def encode(string):
- if PY2 and isinstance(string, unicode):
- string = string.encode('utf-8')
- return string
-
-
-class Logger(object):
+class Logger:
def __init__(self, name=''):
self._name = name
+ def _log(self, msg, level):
+ xbmc.log(f'{self._name}: {msg}', level)
+
def info(self, msg):
- xbmc.log(encode('{}: {}'.format(self._name, msg)), xbmc.LOGINFO)
+ self._log(msg, xbmc.LOGINFO)
def error(self, msg):
- xbmc.log(encode('{}: {}'.format(self._name, msg)), xbmc.LOGERROR)
+ self._log(msg, xbmc.LOGERROR)
def exception(self, msg):
- self.error(msg)
+ self._log(msg, xbmc.LOGERROR)
xbmc.log(format_exc(), xbmc.LOGERROR)
def debug(self, msg):
- xbmc.log(encode('{}: {}'.format(self._name, msg)), xbmc.LOGDEBUG)
+ self._log(msg, xbmc.LOGDEBUG)
def critical(self, msg):
- xbmc.log(encode('{}: {}'.format(self._name, msg)), xbmc.LOGFATAL)
+ self._log(msg, xbmc.LOGFATAL)
def getLogger(name):
diff --git a/script.module.web-pdb/libs/web_pdb/pdb_py2.py b/script.module.web-pdb/libs/web_pdb/pdb_py2.py
deleted file mode 100644
index 2bd2476b56..0000000000
--- a/script.module.web-pdb/libs/web_pdb/pdb_py2.py
+++ /dev/null
@@ -1,56 +0,0 @@
-# coding: utf-8
-# Author: Roman Miroshnychenko aka Roman V.M.
-# E-mail: romanvm@yandex.ua
-#
-# Copyright (c) 2016 Roman Miroshnychenko
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to deal
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be included in
-# all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-# THE SOFTWARE.
-
-import pprint
-from pdb import Pdb
-
-
-class PdbPy2(Pdb):
- """
- This class overrides ``do_p`` and ``do_pp`` methods
- to enable human-readable printing of Unicode strings in Python 2
- """
- def do_p(self, arg):
- try:
- repr_value = repr(self._getval(arg))
- # Try to convert Unicode string to human-readable form
- try:
- repr_value = repr_value.decode('raw_unicode_escape')
- except UnicodeError:
- repr_value = repr_value.decode('utf-8', 'replace')
- print >> self.stdout, repr_value
- except:
- pass
-
- def do_pp(self, arg):
- try:
- repr_value = pprint.pformat(self._getval(arg))
- # Try to convert Unicode string to human-readable form
- try:
- repr_value = repr_value.decode('raw_unicode_escape')
- except UnicodeError:
- repr_value = repr_value.decode('utf-8', 'replace')
- print >> self.stdout, repr_value
- except:
- pass
diff --git a/script.module.web-pdb/libs/web_pdb/web_console.py b/script.module.web-pdb/libs/web_pdb/web_console.py
index 1156dc9b15..91e2767505 100644
--- a/script.module.web-pdb/libs/web_pdb/web_console.py
+++ b/script.module.web-pdb/libs/web_pdb/web_console.py
@@ -1,6 +1,5 @@
-# coding: utf-8
# Author: Roman Miroshnychenko aka Roman V.M.
-# E-mail: romanvm@yandex.ua
+# E-mail: roman1972@gmail.com
#
# Copyright (c) 2016 Roman Miroshnychenko
#
@@ -25,19 +24,17 @@
File-like web-based input/output console
"""
-from __future__ import absolute_import, unicode_literals
+import queue
import weakref
from threading import Thread, Event, RLock
-try:
- import queue
-except ImportError:
- import Queue as queue
-from .asyncore_wsgi import make_server, AsyncWebSocketHandler
+
import xbmc
from xbmcaddon import Addon
from xbmcgui import DialogProgressBG
-from .wsgi_app import app
+
+from .asyncore_wsgi import make_server, AsyncWebSocketHandler
from .logging import getLogger
+from .wsgi_app import app
__all__ = ['WebConsole']
@@ -131,9 +128,7 @@ def closed(self):
def _run_server(self, host, port):
app.frame_data = self._frame_data
httpd = make_server(host, port, app, ws_handler_class=WebConsoleSocket)
- logger.info('Web-PDB: starting web-server on {0}:{1}...'.format(
- httpd.server_name, port)
- )
+ logger.info(f'Web-PDB: starting web-server on {httpd.server_name}:{port}...')
dialog = DialogProgressBG()
started = False
while not (self._stop_all.is_set() or kodi_monitor.abortRequested()):
@@ -170,8 +165,6 @@ def readline(self):
read = readline
def writeline(self, data):
- if isinstance(data, bytes):
- data = data.decode('utf-8')
self._console_history.contents += data
try:
frame_data = self._debugger.get_current_frame_data()
diff --git a/script.module.web-pdb/libs/web_pdb/wsgi_app.py b/script.module.web-pdb/libs/web_pdb/wsgi_app.py
index 03a219e4cd..70ac5b6ea3 100644
--- a/script.module.web-pdb/libs/web_pdb/wsgi_app.py
+++ b/script.module.web-pdb/libs/web_pdb/wsgi_app.py
@@ -1,4 +1,3 @@
-# coding: utf-8
# Author: Roman Miroshnychenko aka Roman V.M.
# E-mail: roman1972@gmail.com
#
@@ -25,11 +24,12 @@
Web-UI WSGI application
"""
+import gzip
import json
import os
-import gzip
-from io import BytesIO
from functools import wraps
+from io import BytesIO
+
import bottle
__all__ = ['app']
@@ -39,11 +39,6 @@
cwd = os.path.dirname(os.path.abspath(__file__))
bottle.TEMPLATE_PATH.append(os.path.join(cwd, 'templates'))
static_path = os.path.join(cwd, 'static')
-try:
- string_type = basestring
-except NameError:
- string_type = (bytes, str)
- unicode = str
def compress(func):
@@ -54,9 +49,9 @@ def compress(func):
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
if ('gzip' in bottle.request.headers.get('Accept-Encoding', '') and
- isinstance(result, string_type) and
+ isinstance(result, (str, bytes)) and
len(result) > 1024):
- if isinstance(result, unicode):
+ if isinstance(result, str):
result = result.encode('utf-8')
tmp_fo = BytesIO()
with gzip.GzipFile(mode='wb', fileobj=tmp_fo) as gzip_fo:
diff --git a/script.module.web-pdb/main.py b/script.module.web-pdb/main.py
index 90e757cf73..5db416819c 100644
--- a/script.module.web-pdb/main.py
+++ b/script.module.web-pdb/main.py
@@ -21,7 +21,6 @@
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
-from __future__ import unicode_literals
from xbmcaddon import Addon
from xbmcgui import Dialog