From 1ccfa9c0876a013761d99f40693f51d2e9b666c7 Mon Sep 17 00:00:00 2001 From: romanvm Date: Sat, 8 Jul 2023 11:14:06 +0000 Subject: [PATCH] [script.module.web-pdb] 1.6.2 --- script.module.web-pdb/addon.xml | 7 +- .../libs/web_pdb/__init__.py | 48 +- .../asyncore_wsgi/SimpleWebSocketServer.py | 40 +- .../libs/web_pdb/asyncore_wsgi/__init__.py | 4 +- .../libs/web_pdb/asyncore_wsgi/asyncore.py | 644 ++++++++++++++++++ script.module.web-pdb/libs/web_pdb/logging.py | 26 +- script.module.web-pdb/libs/web_pdb/pdb_py2.py | 56 -- .../libs/web_pdb/web_console.py | 21 +- .../libs/web_pdb/wsgi_app.py | 15 +- script.module.web-pdb/main.py | 1 - 10 files changed, 699 insertions(+), 163 deletions(-) create mode 100644 script.module.web-pdb/libs/web_pdb/asyncore_wsgi/asyncore.py delete mode 100644 script.module.web-pdb/libs/web_pdb/pdb_py2.py 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