<commit_before>""" NetRNG

    A network connected random number generator

    Copyright 2014 Infincia LLC

    See LICENSE file for license information

"""

from __future__ import absolute_import, print_function, division

__author__ = 'Stephen Oliver'
__maintainer__ = 'Stephen Oliver <steve@infincia.com>'
__version__ = '0.2a1'
__license__ = 'MIT'
__all__ = ['Server', 'Client']

# standard libraries
import time
import sys
import os
import logging
import msgpack
import errno
import signal

# pip packages
import gevent
import gevent.subprocess
import gevent.queue
import gevent.socket as socket
from gevent.server import StreamServer
from gevent.pool import Pool
from gevent.coros import RLock
from gevent import Timeout
from zeroconf import ServiceBrowser, Zeroconf, ServiceInfo

# delimiter for end of socket messages
SOCKET_DELIMITER = '--NETRNG-SOCKET-DELIMITER'

# library logger
log = logging.getLogger('netrng')

class Server(object):
    '''
        NetRNG server

    '''

    def __init__(self,
                 listen_address=None,
                 port=None,
                 max_clients=None,
                 sample_size_bytes=None,
                 hwrng_device=None,
                 use_zeroconf=False):
        log.info('NetRNG server: initializing')

        # Listen address used by the server
        self.listen_address = listen_address

        # TCP port to listen on
        self.port = port

        # Maximum number of clients to accept, this prevents your HWRNG from being
        # overloaded, starving clients. This requires testing and depends entirely on
        # how fast your HWRNG can be read. A device that can spit out 1mbps (100KB/s) could
        # give 100 clients 1KB/s, but a device that can only generate 128bps may only
        # be able to serve 1 client slowly
        self.max_clients = max_clients

        # How much random data to request from the device for each client push
        self.sample_size_bytes = sample_size_bytes

        # Source device to use for random data, should be something fast and
        # high quality, DON'T set this to /dev/random
        self.hwrng_device = hwrng_device

        # open the hwrng for reading later during client requests
        self.hwrng = open(self.hwrng_device, 'r')

        # lock to prevent multiple clients from getting the same random samples
        self.rng_lock = RLock()

        self.use_zeroconf = use_zeroconf

        self.zeroconf_controller = Zeroconf()

    def broadcast_service(self):
        desc = {'version': __version__}
        info = ServiceInfo('_netrng._tcp.local.', '{}._netrng._tcp.local.'.format(socket.gethostname()), socket.inet_aton(self.listen_address), self.port, 0, 0, desc)
        log.info('NetRNG server: registering service with Bonjour: %s', info)
        self.zeroconf_controller.register_service(info)

    def unregister_service(self):
        log.info('NetRNG server: unregistering all bonjour services')
        self.zeroconf_controller.unregister_all_services()

    def serve(self, sock, address):
        '''
            Serves client connections providing random samples to them in a one-to-many
            request response architecture, with locking to ensure each client gets unique
            samples

        '''
        log.debug('NetRNG server: client connected %s', address)

        try:
            while True:
                log.debug('NetRNG server: receive cycle start')
                requestmsg = ""
                with Timeout(3, gevent.Timeout):
                    while True:
                        data = sock.recv(1024)
                        requestmsg = requestmsg + data
                        log.debug('NetRNG server: receive cycle')
                        if SOCKET_DELIMITER in requestmsg:
                            break
                        gevent.sleep()
                requestmsg = requestmsg.replace(SOCKET_DELIMITER, '')
                request = msgpack.unpackb(requestmsg)
                log.debug('NetRNG server: receive cycle done')
                log.debug('NetRNG server: request received %s', request)
                if request['get'] == 'sample':
                    with self.rng_lock:
                        log.debug('NetRNG server: rng lock acquired')
                        sample = self.hwrng.read(self.sample_size_bytes)
                    log.debug('NetRNG server: rng lock released')
                    log.debug('NetRNG server: sending response')
                    responsemsg = msgpack.packb({'push': 'sample', 'sample': sample})
                    sock.sendall(responsemsg + SOCKET_DELIMITER)
                if request['get'] == 'heartbeat':
                    log.debug('NetRNG server: sending heartbeat response to %s', address)
                    responsemsg = msgpack.packb({'push': 'heartbeat'})
                    sock.sendall(responsemsg + SOCKET_DELIMITER)
        except socket.error as e:
            if isinstance(e.args, tuple):
                if e[0] == errno.EPIPE:
                    log.debug('NetRNG server: client disconnected %s', address)
            else:
                log.exception('NetRNG server: socket error %s', e)
        except gevent.Timeout as timeout:
            log.debug('NetRNG server: client socket timeout')
        except Exception as e:
            log.exception('NetRNG server: %s', e)
        finally:
            sock.close()

    def calibrate(self):
        '''
            Naive implementation of auto-calibration for entropy source, should
            check how much entropy can be received in a given number of seconds
            and use that information to decide how much entropy can be distributed
            per second. With that information, it should be possible to decide
            how many clients can be promised `sample_size_bytes` per second

        '''
        log.info('NetRNG server: starting entropy source performance calibration')
        calibration_period = 15 # seconds
        received_entropy = ""
        stop_time = time.time() + calibration_period
        while time.time() < stop_time:
            with self.rng_lock:
                received_entropy += self.hwrng.read(self.sample_size_bytes)
        received_entropy_size = len(received_entropy)
        received_entropy_per_second = received_entropy_size / calibration_period
        log.info('NetRNG server: entropy source can provide %.2f bytes per second', received_entropy_per_second)

    def start(self):
        '''
            Server starts listening on a TCP socket and spawns a greenlet for each
            new connection. Blocks caller.

        '''
        self.pool = Pool(self.max_clients)
        self.server = StreamServer((self.listen_address, self.port), self.serve, spawn=self.pool)
        log.info('NetRNG server: serving up to %d connections on %s:%d)', self.max_clients, self.listen_address, self.port)
        try:
            self.server.start()
            if self.use_zeroconf:
                self.broadcast_service()
            gevent.wait()
        except KeyboardInterrupt as e:
            log.debug('NetRNG server: exiting due to keyboard interrupt')
            sys.exit(0)

    def stop(self):
        '''
            Server stops listening on the TCP socket, stops accepting new connections
            and finally kills spawned connection handlers

        '''
        log.debug('NetRNG server: stopping server and killing existing client connections')
        if self.use_zeroconf:
            self.unregister_service()
        self.server.stop()

class Client(object):
    '''
        NetRNG client

    '''
    def __init__(self, server_address=None, port=None, use_zeroconf=False):
        log.info('NetRNG client: initializing')

        # Address of the server to connect to
        self.server_address = server_address

        # TCP port to connect to on the server
        self.port = port

        # client socket for connecting to server
        self.rngd = gevent.subprocess.Popen(['rngd','-f','-r','/dev/stdin'],
                                               stdin=gevent.subprocess.PIPE,
                                               stdout=open(os.devnull, 'w'),
                                               stderr=open(os.devnull, 'w'),
                                               close_fds=True)

        # client socket for connecting to server
        self.sock = None

        self.use_zeroconf = use_zeroconf

        if self.use_zeroconf:
            self.zeroconf_controller = Zeroconf()
            self.browser = ServiceBrowser(self.zeroconf, "_netrng._tcp.local.", self)

        # queue for pushing received samples to the rngd subprocess as needed
        self.rngd_queue = gevent.queue.Queue(maxsize=10)

    def remove_service(self, zeroconf, type, name):
        self.server_address = None
        self.port = None
        log.debug('Service %s removed' % (name,))

    def add_service(self, zeroconf, type, name):
        info = zeroconf.get_service_info(type, name)
        self.server_address = info['address']
        self.port = info['port']
        log.debug('Service %s added, service info: %s' % (name, info))

    def rngd_handler(self):
        '''
            Iterates over the rngd_queue

        '''
        log.debug('NetRNG client: starting rngd queue greenlet')
        try:
            while True:
                sample = self.rngd_queue.get()
                self.rngd.stdin.write(sample)
                self.rngd.stdin.flush()
                gevent.sleep()
        except gevent.GreenletExit as exit:
            log.debug('NetRNG client: rngd queue greenlet exiting due to graceful quit')
        except OSError:
            return

    def stream(self):
        '''
            Opens a connection to the server, configures the sample size to
            match the server configuration, then starts feeding received samples to rngd
            running in a subprocess.

            Running rngd in a subprocess allows runtime control over
            starting/stopping/configuring it at the right times

        '''
        log.debug('NetRNG client: starting stream greenlet')

        # client socket for connecting to server
        server_socket = None

        # Connection state
        server_connected = False

        while True:
            try:
                if not self.server_address or not self.port:
                    gevent.sleep(1)
                    continue

                if not server_connected:
                    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                    server_socket.connect((self.server_address, self.port))
                    log.debug('NetRNG client: connected to %s:%d', self.server_address, self.port)
                    server_connected = True

                if self.rngd_queue.full():
                    # send a keepalive to the server
                    log.debug('NetRNG client: sending heartbeat message')
                    requestmsg = msgpack.packb({'get': 'heartbeat'})
                    server_socket.sendall(requestmsg + SOCKET_DELIMITER)
                    log.debug('NetRNG client: heartbeat request sent')
                else:
                    # request a new sample
                    log.debug('NetRNG client: requesting sample')
                    requestmsg = msgpack.packb({'get': 'sample'})
                    server_socket.sendall(requestmsg + SOCKET_DELIMITER)
                    log.debug('NetRNG client: sample request sent')

                # wait for response
                log.debug('NetRNG client: receive cycle start')
                responsemsg = ""
                with Timeout(2, gevent.Timeout):
                    while True:
                        data = server_socket.recv(1024)
                        responsemsg = responsemsg + data
                        log.debug('NetRNG client: receive cycle')
                        if SOCKET_DELIMITER in responsemsg:
                            break
                        gevent.sleep()

                responsemsg = responsemsg.replace(SOCKET_DELIMITER, '')
                response = msgpack.unpackb(responsemsg)
                log.debug('NetRNG client: receive cycle done')

                if response['push'] == 'sample':
                    sample = response['sample']
                    log.debug('NetRNG client: received %d byte sample', len(sample))
                    self.rngd_queue.put(sample)
                elif response['push'] == 'heartbeat':
                    log.debug('NetRNG client: received heartbeat response')
                    gevent.sleep(1)
                else:
                    log.debug('NetRNG client: received unknown response from server')

            except socket.error as socket_exception:
                log.debug('NetRNG client: server unavailable, reconnecting in 10 seconds')
                server_connected = False
                server_socket.close()
                gevent.sleep(10)
            except gevent.Timeout as timeout:
                log.debug('NetRNG client: server socket timeout')
                server_connected = False
                server_socket.close()
                gevent.sleep(1)
            except KeyboardInterrupt as keyboard_exception:
                log.debug('NetRNG client: exiting due to keyboard interrupt')
                server_connected = False
                server_socket.close()
                break
            except gevent.GreenletExit as exit:
                log.debug('NetRNG client: stream greenlet exiting due to graceful quit')
                server_connected = False
                server_socket.close()
                break
            except Exception as unknown_exception:
                log.exception('NetRNG client: unknown exception %s', unknown_exception)
                server_connected = False
                server_socket.close()
        sys.exit(0)

    def start(self):
        '''
            Client spawns a greenlet for the rngd handler and the network stream
            connection, then joins and waits for them to block the caller

        '''
        log.debug('NetRNG client: spawning greenlets for rngd and stream')
        try:
            rngd_greenlet = gevent.spawn(self.rngd_handler)
            stream_greenlet = gevent.spawn(self.stream)
            greenlets = [stream_greenlet, rngd_greenlet]
            gevent.joinall(greenlets)
        except KeyboardInterrupt as e:
            log.debug('NetRNG client: exiting due to keyboard interrupt')
        finally:
            gevent.killall(greenlets)
            sys.exit(0)
<commit_msg>Fixes for zeroconf<commit_after>""" NetRNG

    A network connected random number generator

    Copyright 2014 Infincia LLC

    See LICENSE file for license information

"""

from __future__ import absolute_import, print_function, division

__author__ = 'Stephen Oliver'
__maintainer__ = 'Stephen Oliver <steve@infincia.com>'
__version__ = '0.2a1'
__license__ = 'MIT'
__all__ = ['Server', 'Client']

# standard libraries
import time
import sys
import os
import logging
import msgpack
import errno
import signal

# pip packages
import gevent
import gevent.subprocess
import gevent.queue
import gevent.socket as socket
from gevent.server import StreamServer
from gevent.pool import Pool
from gevent.coros import RLock
from gevent import Timeout
from zeroconf import ServiceBrowser, Zeroconf, ServiceInfo

# delimiter for end of socket messages
SOCKET_DELIMITER = '--NETRNG-SOCKET-DELIMITER'

# library logger
log = logging.getLogger('netrng')

class Server(object):
    '''
        NetRNG server

    '''

    def __init__(self,
                 listen_address=None,
                 port=None,
                 max_clients=None,
                 sample_size_bytes=None,
                 hwrng_device=None,
                 use_zeroconf=False):
        log.info('NetRNG server: initializing')

        # Listen address used by the server
        self.listen_address = listen_address

        # TCP port to listen on
        self.port = port

        # Maximum number of clients to accept, this prevents your HWRNG from being
        # overloaded, starving clients. This requires testing and depends entirely on
        # how fast your HWRNG can be read. A device that can spit out 1mbps (100KB/s) could
        # give 100 clients 1KB/s, but a device that can only generate 128bps may only
        # be able to serve 1 client slowly
        self.max_clients = max_clients

        # How much random data to request from the device for each client push
        self.sample_size_bytes = sample_size_bytes

        # Source device to use for random data, should be something fast and
        # high quality, DON'T set this to /dev/random
        self.hwrng_device = hwrng_device

        # open the hwrng for reading later during client requests
        self.hwrng = open(self.hwrng_device, 'r')

        # lock to prevent multiple clients from getting the same random samples
        self.rng_lock = RLock()

        self.use_zeroconf = use_zeroconf

        self.zeroconf_controller = Zeroconf()

    def broadcast_service(self):
        if self.listen_address == '0.0.0.0':
            raise Exception('NetRNG server: zeroconf cannot use %s as a listen address, please set a listen address in /etc/netrng.conf' % self.listen_address)
        desc = {'version': __version__}
        info = ServiceInfo('_netrng._tcp.local.', '{}._netrng._tcp.local.'.format(socket.gethostname()), socket.inet_aton(self.listen_address), self.port, 0, 0, desc)
        log.info('NetRNG server: registering service with Bonjour: %s', info)
        self.zeroconf_controller.register_service(info)

    def unregister_service(self):
        log.info('NetRNG server: unregistering all bonjour services')
        self.zeroconf_controller.unregister_all_services()

    def serve(self, sock, address):
        '''
            Serves client connections providing random samples to them in a one-to-many
            request response architecture, with locking to ensure each client gets unique
            samples

        '''
        log.debug('NetRNG server: client connected %s', address)

        try:
            while True:
                log.debug('NetRNG server: receive cycle start')
                requestmsg = ""
                with Timeout(3, gevent.Timeout):
                    while True:
                        data = sock.recv(1024)
                        requestmsg = requestmsg + data
                        log.debug('NetRNG server: receive cycle')
                        if SOCKET_DELIMITER in requestmsg:
                            break
                        gevent.sleep()
                requestmsg = requestmsg.replace(SOCKET_DELIMITER, '')
                request = msgpack.unpackb(requestmsg)
                log.debug('NetRNG server: receive cycle done')
                log.debug('NetRNG server: request received %s', request)
                if request['get'] == 'sample':
                    with self.rng_lock:
                        log.debug('NetRNG server: rng lock acquired')
                        sample = self.hwrng.read(self.sample_size_bytes)
                    log.debug('NetRNG server: rng lock released')
                    log.debug('NetRNG server: sending response')
                    responsemsg = msgpack.packb({'push': 'sample', 'sample': sample})
                    sock.sendall(responsemsg + SOCKET_DELIMITER)
                if request['get'] == 'heartbeat':
                    log.debug('NetRNG server: sending heartbeat response to %s', address)
                    responsemsg = msgpack.packb({'push': 'heartbeat'})
                    sock.sendall(responsemsg + SOCKET_DELIMITER)
        except socket.error as e:
            if isinstance(e.args, tuple):
                if e[0] == errno.EPIPE:
                    log.debug('NetRNG server: client disconnected %s', address)
            else:
                log.exception('NetRNG server: socket error %s', e)
        except gevent.Timeout as timeout:
            log.debug('NetRNG server: client socket timeout')
        except Exception as e:
            log.exception('NetRNG server: %s', e)
        finally:
            sock.close()

    def calibrate(self):
        '''
            Naive implementation of auto-calibration for entropy source, should
            check how much entropy can be received in a given number of seconds
            and use that information to decide how much entropy can be distributed
            per second. With that information, it should be possible to decide
            how many clients can be promised `sample_size_bytes` per second

        '''
        log.info('NetRNG server: starting entropy source performance calibration')
        calibration_period = 15 # seconds
        received_entropy = ""
        stop_time = time.time() + calibration_period
        while time.time() < stop_time:
            with self.rng_lock:
                received_entropy += self.hwrng.read(self.sample_size_bytes)
        received_entropy_size = len(received_entropy)
        received_entropy_per_second = received_entropy_size / calibration_period
        log.info('NetRNG server: entropy source can provide %.2f bytes per second', received_entropy_per_second)

    def start(self):
        '''
            Server starts listening on a TCP socket and spawns a greenlet for each
            new connection. Blocks caller.

        '''
        self.pool = Pool(self.max_clients)
        self.server = StreamServer((self.listen_address, self.port), self.serve, spawn=self.pool)
        log.info('NetRNG server: serving up to %d connections on %s:%d)', self.max_clients, self.listen_address, self.port)
        try:
            self.server.start()
            if self.use_zeroconf:
                self.broadcast_service()
            gevent.wait()
        except KeyboardInterrupt as e:
            log.debug('NetRNG server: exiting due to keyboard interrupt')
            sys.exit(0)

    def stop(self):
        '''
            Server stops listening on the TCP socket, stops accepting new connections
            and finally kills spawned connection handlers

        '''
        log.debug('NetRNG server: stopping server and killing existing client connections')
        if self.use_zeroconf:
            self.unregister_service()
        self.server.stop()

class Client(object):
    '''
        NetRNG client

    '''
    def __init__(self, server_address=None, port=None, use_zeroconf=False):
        log.info('NetRNG client: initializing')

        # client socket for connecting to server
        self.rngd = gevent.subprocess.Popen(['rngd','-f','-r','/dev/stdin'],
                                               stdin=gevent.subprocess.PIPE,
                                               stdout=open(os.devnull, 'w'),
                                               stderr=open(os.devnull, 'w'),
                                               close_fds=True)

        # client socket for connecting to server
        self.sock = None

        self.use_zeroconf = use_zeroconf

        if self.use_zeroconf:
            # Address of the server to connect to
            self.server_address = None

            # TCP port to connect to on the server
            self.port = None

            self.zeroconf_controller = Zeroconf()
            self.browser = ServiceBrowser(self.zeroconf_controller, "_netrng._tcp.local.", self)
        else:
            # Address of the server to connect to
            self.server_address = server_address

            # TCP port to connect to on the server
            self.port = port

        # queue for pushing received samples to the rngd subprocess as needed
        self.rngd_queue = gevent.queue.Queue(maxsize=10)

    def remove_service(self, zeroconf, type, name):
        self.server_address = None
        self.port = None
        log.debug('Service %s removed' % (name,))

    def add_service(self, zeroconf, type, name):
        info = zeroconf.get_service_info(type, name)
        self.server_address = socket.inet_ntoa(info.address)
        self.port = info.port
        log.debug('Service %s added, service info: %s' % (name, info))

    def rngd_handler(self):
        '''
            Iterates over the rngd_queue

        '''
        log.debug('NetRNG client: starting rngd queue greenlet')
        try:
            while True:
                sample = self.rngd_queue.get()
                self.rngd.stdin.write(sample)
                self.rngd.stdin.flush()
                gevent.sleep()
        except gevent.GreenletExit as exit:
            log.debug('NetRNG client: rngd queue greenlet exiting due to graceful quit')
        except OSError:
            return

    def stream(self):
        '''
            Opens a connection to the server, configures the sample size to
            match the server configuration, then starts feeding received samples to rngd
            running in a subprocess.

            Running rngd in a subprocess allows runtime control over
            starting/stopping/configuring it at the right times

        '''
        log.debug('NetRNG client: starting stream greenlet')

        # client socket for connecting to server
        server_socket = None

        # Connection state
        server_connected = False

        while True:
            try:
                if not self.server_address or not self.port:
                    gevent.sleep(1)
                    continue

                if not server_connected:
                    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                    server_socket.connect((self.server_address, self.port))
                    log.debug('NetRNG client: connected to %s:%d', self.server_address, self.port)
                    server_connected = True

                if self.rngd_queue.full():
                    # send a keepalive to the server
                    log.debug('NetRNG client: sending heartbeat message')
                    requestmsg = msgpack.packb({'get': 'heartbeat'})
                    server_socket.sendall(requestmsg + SOCKET_DELIMITER)
                    log.debug('NetRNG client: heartbeat request sent')
                else:
                    # request a new sample
                    log.debug('NetRNG client: requesting sample')
                    requestmsg = msgpack.packb({'get': 'sample'})
                    server_socket.sendall(requestmsg + SOCKET_DELIMITER)
                    log.debug('NetRNG client: sample request sent')

                # wait for response
                log.debug('NetRNG client: receive cycle start')
                responsemsg = ""
                with Timeout(2, gevent.Timeout):
                    while True:
                        data = server_socket.recv(1024)
                        responsemsg = responsemsg + data
                        log.debug('NetRNG client: receive cycle')
                        if SOCKET_DELIMITER in responsemsg:
                            break
                        gevent.sleep()

                responsemsg = responsemsg.replace(SOCKET_DELIMITER, '')
                response = msgpack.unpackb(responsemsg)
                log.debug('NetRNG client: receive cycle done')

                if response['push'] == 'sample':
                    sample = response['sample']
                    log.debug('NetRNG client: received %d byte sample', len(sample))
                    self.rngd_queue.put(sample)
                elif response['push'] == 'heartbeat':
                    log.debug('NetRNG client: received heartbeat response')
                    gevent.sleep(1)
                else:
                    log.debug('NetRNG client: received unknown response from server')

            except socket.error as socket_exception:
                log.debug('NetRNG client: server unavailable, reconnecting in 10 seconds')
                server_connected = False
                server_socket.close()
                gevent.sleep(10)
            except gevent.Timeout as timeout:
                log.debug('NetRNG client: server socket timeout')
                server_connected = False
                server_socket.close()
                gevent.sleep(1)
            except KeyboardInterrupt as keyboard_exception:
                log.debug('NetRNG client: exiting due to keyboard interrupt')
                server_connected = False
                server_socket.close()
                break
            except gevent.GreenletExit as exit:
                log.debug('NetRNG client: stream greenlet exiting due to graceful quit')
                server_connected = False
                server_socket.close()
                break
            except Exception as unknown_exception:
                log.exception('NetRNG client: unknown exception %s', unknown_exception)
                server_connected = False
                server_socket.close()
        sys.exit(0)

    def start(self):
        '''
            Client spawns a greenlet for the rngd handler and the network stream
            connection, then joins and waits for them to block the caller

        '''
        log.debug('NetRNG client: spawning greenlets for rngd and stream')
        try:
            rngd_greenlet = gevent.spawn(self.rngd_handler)
            stream_greenlet = gevent.spawn(self.stream)
            greenlets = [stream_greenlet, rngd_greenlet]
            gevent.joinall(greenlets)
        except KeyboardInterrupt as e:
            log.debug('NetRNG client: exiting due to keyboard interrupt')
        finally:
            gevent.killall(greenlets)
            sys.exit(0)
<|endoftext|>


<commit_before>import os
import shlex

from coalib.bearlib.abstractions.Linter import linter
from coalib.bears.requirements.PipRequirement import PipRequirement
from coalib.results.RESULT_SEVERITY import RESULT_SEVERITY
from coalib.settings.Setting import typed_list

@linter(executable='pylint',
        output_format='regex',
        output_regex=r'L(?P<line>\d+)C(?P<column>\d+): (?P<message>'
                     r'(?P<origin>(?P<severity>[WFECRI])\d+) - .*)',
        severity_map={'F': RESULT_SEVERITY.MAJOR,
                      'E': RESULT_SEVERITY.MAJOR,
                      'W': RESULT_SEVERITY.NORMAL,
                      'C': RESULT_SEVERITY.INFO,
                      'R': RESULT_SEVERITY.INFO,
                      'I': RESULT_SEVERITY.INFO})
class PyLintBear:
    """
    Checks the code with pylint. This will run pylint over each file
    separately.
    """
    LANGUAGES = {"Python", "Python 2", "Python 3"}
    REQUIREMENTS = {PipRequirement('pylint', '1.*')}
    AUTHORS = {'The coala developers'}
    AUTHORS_EMAILS = {'coala-devel@googlegroups.com'}
    LICENSE = 'AGPL-3.0'

    @staticmethod
    def create_arguments(filename, file, config_file,
                         pylint_disable: typed_list(str)=None,
                         pylint_enable: typed_list(str)=None,
                         pylint_cli_options: str="",
                         pylint_rcfile: str=""):
        """
        :param pylint_disable:     Disable the message, report, category or
                                   checker with the given id(s).
        :param pylint_enable:      Enable the message, report, category or
                                   checker with the given id(s).
        :param pylint_cli_options: Any command line options you wish to be
                                   passed to pylint.
        :param pylint_rcfile:      The rcfile for PyLint.
        """
        args = ('--reports=n', '--persistent=n',
                '--msg-template="L{line}C{column}: {msg_id} - {msg}"')
        if pylint_disable:
            args += ('--disable=' + ','.join(pylint_disable),)
        if pylint_enable:
            args += ('--enable=' + ','.join(pylint_enable),)
        if pylint_cli_options:
            args += tuple(shlex.split(pylint_cli_options))
        if pylint_rcfile:
            args += ('--rcfile=' + pylint_rcfile,)
        else:
            args += ('--rcfile=' + os.devnull,)

        return args + (filename,)
<commit_msg>PyLintBear: Add CAN_DETECT<commit_after>import os
import shlex

from coalib.bearlib.abstractions.Linter import linter
from coalib.bears.requirements.PipRequirement import PipRequirement
from coalib.results.RESULT_SEVERITY import RESULT_SEVERITY
from coalib.settings.Setting import typed_list

@linter(executable='pylint',
        output_format='regex',
        output_regex=r'L(?P<line>\d+)C(?P<column>\d+): (?P<message>'
                     r'(?P<origin>(?P<severity>[WFECRI])\d+) - .*)',
        severity_map={'F': RESULT_SEVERITY.MAJOR,
                      'E': RESULT_SEVERITY.MAJOR,
                      'W': RESULT_SEVERITY.NORMAL,
                      'C': RESULT_SEVERITY.INFO,
                      'R': RESULT_SEVERITY.INFO,
                      'I': RESULT_SEVERITY.INFO})
class PyLintBear:
    """
    Checks the code with pylint. This will run pylint over each file
    separately.
    """
    LANGUAGES = {"Python", "Python 2", "Python 3"}
    REQUIREMENTS = {PipRequirement('pylint', '1.*')}
    AUTHORS = {'The coala developers'}
    AUTHORS_EMAILS = {'coala-devel@googlegroups.com'}
    LICENSE = 'AGPL-3.0'
    CAN_DETECT = {'Unused Code', 'Formatting', 'Duplication', 'Security',
                  'Syntax'}

    @staticmethod
    def create_arguments(filename, file, config_file,
                         pylint_disable: typed_list(str)=None,
                         pylint_enable: typed_list(str)=None,
                         pylint_cli_options: str="",
                         pylint_rcfile: str=""):
        """
        :param pylint_disable:     Disable the message, report, category or
                                   checker with the given id(s).
        :param pylint_enable:      Enable the message, report, category or
                                   checker with the given id(s).
        :param pylint_cli_options: Any command line options you wish to be
                                   passed to pylint.
        :param pylint_rcfile:      The rcfile for PyLint.
        """
        args = ('--reports=n', '--persistent=n',
                '--msg-template="L{line}C{column}: {msg_id} - {msg}"')
        if pylint_disable:
            args += ('--disable=' + ','.join(pylint_disable),)
        if pylint_enable:
            args += ('--enable=' + ','.join(pylint_enable),)
        if pylint_cli_options:
            args += tuple(shlex.split(pylint_cli_options))
        if pylint_rcfile:
            args += ('--rcfile=' + pylint_rcfile,)
        else:
            args += ('--rcfile=' + os.devnull,)

        return args + (filename,)
<|endoftext|>


<commit_before>"""A library for creating graphs using Unicode braille characters.

https://pypi.python.org/pypi/braillegraph

Someone on reddit posted a screenshot of their xmobar setup, which used braille
characters to show the loads of their four processor cores, as well as several
other metrics. I was impressed that you could fit so much data into a single
line. I immediately set out to implement braille bar graphs for myself.

The characters this script outputs are in the Unicode Braille Patterns section,
code points 0x2800 through 0x28FF. Not all fonts support these characters, so
if you can't see the examples below check your font settings.

There are two ways to use this package: imported in Python code, or as a
command line script.

To use the package in Python, import it and use the vertical_graph and
horizontal_graph functions.

    >>> from braillegraph import vertical_graph, horizontal_graph
    >>> vertical_graph([3, 1, 4, 1])
    '⡯⠥'
    >>> horizontal_graph([3, 1, 4, 1])
    '⣆⣇'

To use the package as a script, run it as

    % python -m braillegraph vertical 3 1 4 1 5 9 2 6
    ⡯⠥
    ⣿⣛⣓⠒⠂
    % python -m braillegraph horizontal 3 1 4 1 5 9 2 6
    ⠀⠀⢀
    ⠀⠀⣸⢠
    ⣆⣇⣿⣼

For a description of the arguments and flags, run

    % python -m braillegraph --help
"""

import itertools

# Only export this function. Everything else is an implementation detail.
__all__ = ['vertical_graph', 'horizontal_graph']

# The Unicode codepoint for an empty braille blocks (no raised dots)
_BRAILLE_EMPTY_BLOCK = 0x2800

# The offsets to add for dots. The dots are represented as an 8-bit bitfield as
# follows:
#
#     0x01  0x08
#     0x02  0x10
#     0x04  0x20
#     0x40  0x80
#
# Half rows only have a left-most dot. Full rows have both dots.
_BRAILLE_HALF_ROW = [0x01, 0x02, 0x04, 0x40]
_BRAILLE_FULL_ROW = [0x09, 0x12, 0x24, 0xC0]

# Full columns have all four dots. Partial columns have dots starting from the
# bottom of the block. Note that _BRAILLE_PARTIAL_COL[x][y] represents (y + 1)
# dots.
_BRAILLE_FULL_COL = [0x47, 0xB8]
_BRAILLE_PARTIAL_COL = [
    [0x40, 0x44, 0x46],
    [0x80, 0xA0, 0xB0]
]

def _chunk(iterable, size):
    """Split an iterable into chunks of a fixed size."""
    # We're going to use some star magic to chunk the iterable. We create a
    # copy of the iterator size times, then pull a value from each to form a
    # chunk. The last chunk may have some trailing Nones if the length of the
    # iterable isn't a multiple of size, so we filter them out.

    args = (iter(iterable),) * size

    return (
        # pylint: disable=star-args
        itertools.takewhile(lambda x: x is not None, group)
        for group in itertools.zip_longest(*args)
    )

def _matrix_add_column(matrix, column, default=0):
    """Given a matrix as a list of lists, add a column to the right, filling in
    with a default value if necessary.
    """
    extra_rows_needed = len(column) - len(matrix)
    width = len(matrix[0]) if matrix else 0
    offset = 0

    # If we need extra rows, add them to the top of the matrix.
    if extra_rows_needed > 0:
        for _ in range(extra_rows_needed):
            matrix.insert(0, [default] * width)

    # If the column is shorter, we'll need to shift it down.
    if extra_rows_needed < 0:
        offset = -extra_rows_needed
        column = ([default] * offset) + column

    for index, value in enumerate(column):
        matrix[index].append(value)

def vertical_graph(*args, sep='\n'):
    """Consume an iterable of integers and produce a vertical bar graph using
    braille characters.

    The graph is vertical in that its dependent axis is the vertical axis. Thus
    each value is represented as a row running left to right, and values are
    listed top to bottom.

    If the iterable contains more than four integers, it will be chunked into
    groups of four, separated with newlines by default.

        >>> vertical_graph([1, 2, 3, 4])
        '⣷⣄'
        >>> vertical_graph([1, 2, 3, 4, 5, 6])
        '⣷⣄\\n⠛⠛⠓'
        >>> print(vertical_graph([1, 2, 3, 4, 5, 6]))
        ⣷⣄
        ⠛⠛⠓

    Alternately, the arguments can be passed directly:

        >>> vertical_graph(1, 2, 3, 4)
        '⣷⣄'

    The optional sep parameter controls how groups are separated. If sep is not
    passed (or if it is None), they are put on their own lines. For example, to
    keep everything on one line, space could be used:

        >>> vertical_graph(3, 1, 4, 1, 5, 9, 2, 6, sep=' ')
        '⡯⠥ ⣿⣛⣓⠒⠂'
    """
    lines = []

    # If the arguments were passed as a single iterable, pull it out.
    # Otherwise, just use them as-is.
    if len(args) == 1:
        bars = args[0]
    else:
        bars = args

    # Make sure we use the default when needed
    if sep is None:
        sep = '\n'

    # Break the bars into groups of four, one for each row in the braille
    # blocks.
    for bar_group in _chunk(bars, 4):
        line = []

        for braille_row, bar_value in enumerate(bar_group):
            # The number of full braille blocks needed to draw this bar. Each
            # block is two dots wide.
            full_blocks_needed = bar_value // 2

            # The number of braille blocks needed to draw this bar. The second
            # term accounts for a possible half row.
            blocks_needed = full_blocks_needed + (bar_value % 2)

            # The number of braille blocks we'll need to append to the current
            # line to accomodate this bar
            extra_blocks_needed = blocks_needed - len(line)

            # If we need extra blocks, add them.
            if extra_blocks_needed > 0:
                line.extend([_BRAILLE_EMPTY_BLOCK] * extra_blocks_needed)

            # Fill in the majority of the bar with full braille rows (two dots).
            for block_index in range(full_blocks_needed):
                line[block_index] += _BRAILLE_FULL_ROW[braille_row]

            # If the bar's value is odd, we'll need to add a single dot at the
            # end.
            if bar_value % 2:
                line[full_blocks_needed] += _BRAILLE_HALF_ROW[braille_row]

        # Wrap up this line by converting all the code points to characters
        # and concatenating them.
        lines.append(''.join(chr(code) for code in line))

    # Join all the lines to make the final graph
    return sep.join(lines)

def horizontal_graph(*args):
    """Consume an iterable of integers and produce a horizontal bar graph using
    braille characters.

    The graph is horizontal in that its dependent axis is the horizontal axis.
    Thus each value is represented as a column running bottom to top, and
    values are listed left to right.

    The graph is anchored to the bottom, so columns fill in from the bottom of
    the current braille character and the next character is added on top when
    needed. For columns with no dots, the blank braille character is used, not
    a space character.

        >>> horizontal_graph([1, 2, 3, 4])
        '⣠⣾'
        >>> horizontal_graph([1, 2, 3, 4, 5, 6])
        '⠀⠀⣠\\n⣠⣾⣿'
        >>> print(horizontal_graph([1, 2, 3, 4, 5, 6]))
        ⠀⠀⣠
        ⣠⣾⣿

    Alternately, the arguments can be passed directly:

        >>> horizontal_graph(1, 2, 3, 4)
        '⣠⣾'
    """
    lines = []

    # If the arguments were passed as a single iterable, pull it out.
    # Otherwise, just use them as-is.
    if len(args) == 1:

        bars = args[0]
    else:
        bars = args

    # Break the bars into groups of two, one for each column in the braille
    # blocks.
    for bar_group in _chunk(bars, 2):
        column = []

        for braille_col, bar_value in enumerate(bar_group):
            # The number of full braille blocks needed to draw this bar. Each
            # block is four dots tall.
            full_blocks_needed = bar_value // 4

            # The number of braille blocks needed to draw this bar. This
            # accounts for a possible partial block.
            blocks_needed = full_blocks_needed + (1 if bar_value % 4 else 0)

            # The number of new lines we'll need to prepend to accomodate this
            # bar
            extra_blocks_needed = blocks_needed - len(column)

            # If we need extra blocks, add them.
            column = ([_BRAILLE_EMPTY_BLOCK] * extra_blocks_needed) + column

            # Fill in the majority of the column with full braille colums (four
            # dots). We negate the index to access from the end of the list.
            for block_index in range(-full_blocks_needed, 0, 1):
                column[block_index] += _BRAILLE_FULL_COL[braille_col]

            # If we need a partial column, fill it in. We negate the index to
            # access from the end of the list.
            if bar_value % 4:
                partial_index = (bar_value % 4) - 1
                column[-blocks_needed] += (
                    _BRAILLE_PARTIAL_COL[braille_col][partial_index]
                )

        # Add this column to the lines.
        _matrix_add_column(lines, column, default=_BRAILLE_EMPTY_BLOCK)

    # Convert all the code points into characters, concatenate them into lines,
    # then concatenate all the lines to make the final graph.
    return '\n'.join(''.join(chr(code) for code in line) for line in lines)

<commit_msg>Support doctest in our docstrings.<commit_after>"""A library for creating graphs using Unicode braille characters.

https://pypi.python.org/pypi/braillegraph

Someone on reddit posted a screenshot of their xmobar setup, which used braille
characters to show the loads of their four processor cores, as well as several
other metrics. I was impressed that you could fit so much data into a single
line. I immediately set out to implement braille bar graphs for myself.

The characters this script outputs are in the Unicode Braille Patterns section,
code points 0x2800 through 0x28FF. Not all fonts support these characters, so
if you can't see the examples below check your font settings.

There are two ways to use this package: imported in Python code, or as a
command line script.

To use the package in Python, import it and use the vertical_graph and
horizontal_graph functions.

>>> from braillegraph import vertical_graph, horizontal_graph
>>> vertical_graph([3, 1, 4, 1])
'⡯⠥'
>>> horizontal_graph([3, 1, 4, 1])
'⣆⣇'

To use the package as a script, run it as

% python -m braillegraph vertical 3 1 4 1 5 9 2 6
⡯⠥
⣿⣛⣓⠒⠂
% python -m braillegraph horizontal 3 1 4 1 5 9 2 6
⠀⠀⢀
⠀⠀⣸⢠
⣆⣇⣿⣼

For a description of the arguments and flags, run

% python -m braillegraph --help
"""

import itertools

# Only export this function. Everything else is an implementation detail.
__all__ = ['vertical_graph', 'horizontal_graph']

# The Unicode codepoint for an empty braille blocks (no raised dots)
_BRAILLE_EMPTY_BLOCK = 0x2800

# The offsets to add for dots. The dots are represented as an 8-bit bitfield as
# follows:
#
#     0x01  0x08
#     0x02  0x10
#     0x04  0x20
#     0x40  0x80
#
# Half rows only have a left-most dot. Full rows have both dots.
_BRAILLE_HALF_ROW = [0x01, 0x02, 0x04, 0x40]
_BRAILLE_FULL_ROW = [0x09, 0x12, 0x24, 0xC0]

# Full columns have all four dots. Partial columns have dots starting from the
# bottom of the block. Note that _BRAILLE_PARTIAL_COL[x][y] represents (y + 1)
# dots.
_BRAILLE_FULL_COL = [0x47, 0xB8]
_BRAILLE_PARTIAL_COL = [
    [0x40, 0x44, 0x46],
    [0x80, 0xA0, 0xB0]
]

def _chunk(iterable, size):
    """Split an iterable into chunks of a fixed size."""
    # We're going to use some star magic to chunk the iterable. We create a
    # copy of the iterator size times, then pull a value from each to form a
    # chunk. The last chunk may have some trailing Nones if the length of the
    # iterable isn't a multiple of size, so we filter them out.

    args = (iter(iterable),) * size

    return (
        # pylint: disable=star-args
        itertools.takewhile(lambda x: x is not None, group)
        for group in itertools.zip_longest(*args)
    )

def _matrix_add_column(matrix, column, default=0):
    """Given a matrix as a list of lists, add a column to the right, filling in
    with a default value if necessary.
    """
    extra_rows_needed = len(column) - len(matrix)
    width = len(matrix[0]) if matrix else 0
    offset = 0

    # If we need extra rows, add them to the top of the matrix.
    if extra_rows_needed > 0:
        for _ in range(extra_rows_needed):
            matrix.insert(0, [default] * width)

    # If the column is shorter, we'll need to shift it down.
    if extra_rows_needed < 0:
        offset = -extra_rows_needed
        column = ([default] * offset) + column

    for index, value in enumerate(column):
        matrix[index].append(value)

def vertical_graph(*args, sep='\n'):
    r"""Consume an iterable of integers and produce a vertical bar graph using
    braille characters.

    The graph is vertical in that its dependent axis is the vertical axis. Thus
    each value is represented as a row running left to right, and values are
    listed top to bottom.

    If the iterable contains more than four integers, it will be chunked into
    groups of four, separated with newlines by default.

    >>> vertical_graph([1, 2, 3, 4])
    '⣷⣄'
    >>> vertical_graph([1, 2, 3, 4, 5, 6])
    '⣷⣄\n⠛⠛⠓'
    >>> print(vertical_graph([1, 2, 3, 4, 5, 6]))
    ⣷⣄
    ⠛⠛⠓

    Alternately, the arguments can be passed directly:

    >>> vertical_graph(1, 2, 3, 4)
    '⣷⣄'

    The optional sep parameter controls how groups are separated. If sep is not
    passed (or if it is None), they are put on their own lines. For example, to
    keep everything on one line, space could be used:

    >>> vertical_graph(3, 1, 4, 1, 5, 9, 2, 6, sep=' ')
    '⡯⠥ ⣿⣛⣓⠒⠂'

    """
    lines = []

    # If the arguments were passed as a single iterable, pull it out.
    # Otherwise, just use them as-is.
    if len(args) == 1:
        bars = args[0]
    else:
        bars = args

    # Make sure we use the default when needed
    if sep is None:
        sep = '\n'

    # Break the bars into groups of four, one for each row in the braille
    # blocks.
    for bar_group in _chunk(bars, 4):
        line = []

        for braille_row, bar_value in enumerate(bar_group):
            # The number of full braille blocks needed to draw this bar. Each
            # block is two dots wide.
            full_blocks_needed = bar_value // 2

            # The number of braille blocks needed to draw this bar. The second
            # term accounts for a possible half row.
            blocks_needed = full_blocks_needed + (bar_value % 2)

            # The number of braille blocks we'll need to append to the current
            # line to accomodate this bar
            extra_blocks_needed = blocks_needed - len(line)

            # If we need extra blocks, add them.
            if extra_blocks_needed > 0:
                line.extend([_BRAILLE_EMPTY_BLOCK] * extra_blocks_needed)

            # Fill in the majority of the bar with full braille rows (two dots).
            for block_index in range(full_blocks_needed):
                line[block_index] += _BRAILLE_FULL_ROW[braille_row]

            # If the bar's value is odd, we'll need to add a single dot at the
            # end.
            if bar_value % 2:
                line[full_blocks_needed] += _BRAILLE_HALF_ROW[braille_row]

        # Wrap up this line by converting all the code points to characters
        # and concatenating them.
        lines.append(''.join(chr(code) for code in line))

    # Join all the lines to make the final graph
    return sep.join(lines)

def horizontal_graph(*args):
    r"""Consume an iterable of integers and produce a horizontal bar graph using
    braille characters.

    The graph is horizontal in that its dependent axis is the horizontal axis.
    Thus each value is represented as a column running bottom to top, and
    values are listed left to right.

    The graph is anchored to the bottom, so columns fill in from the bottom of
    the current braille character and the next character is added on top when
    needed. For columns with no dots, the blank braille character is used, not
    a space character.

    >>> horizontal_graph([1, 2, 3, 4])
    '⣠⣾'
    >>> horizontal_graph([1, 2, 3, 4, 5, 6])
    '⠀⠀⣠\n⣠⣾⣿'
    >>> print(horizontal_graph([1, 2, 3, 4, 5, 6]))
    ⠀⠀⣠
    ⣠⣾⣿

    Alternately, the arguments can be passed directly:

    >>> horizontal_graph(1, 2, 3, 4)
    '⣠⣾'

    """
    lines = []

    # If the arguments were passed as a single iterable, pull it out.
    # Otherwise, just use them as-is.
    if len(args) == 1:

        bars = args[0]
    else:
        bars = args

    # Break the bars into groups of two, one for each column in the braille
    # blocks.
    for bar_group in _chunk(bars, 2):
        column = []

        for braille_col, bar_value in enumerate(bar_group):
            # The number of full braille blocks needed to draw this bar. Each
            # block is four dots tall.
            full_blocks_needed = bar_value // 4

            # The number of braille blocks needed to draw this bar. This
            # accounts for a possible partial block.
            blocks_needed = full_blocks_needed + (1 if bar_value % 4 else 0)

            # The number of new lines we'll need to prepend to accomodate this
            # bar
            extra_blocks_needed = blocks_needed - len(column)

            # If we need extra blocks, add them.
            column = ([_BRAILLE_EMPTY_BLOCK] * extra_blocks_needed) + column

            # Fill in the majority of the column with full braille colums (four
            # dots). We negate the index to access from the end of the list.
            for block_index in range(-full_blocks_needed, 0, 1):
                column[block_index] += _BRAILLE_FULL_COL[braille_col]

            # If we need a partial column, fill it in. We negate the index to
            # access from the end of the list.
            if bar_value % 4:
                partial_index = (bar_value % 4) - 1
                column[-blocks_needed] += (
                    _BRAILLE_PARTIAL_COL[braille_col][partial_index]
                )

        # Add this column to the lines.
        _matrix_add_column(lines, column, default=_BRAILLE_EMPTY_BLOCK)

    # Convert all the code points into characters, concatenate them into lines,
    # then concatenate all the lines to make the final graph.
    return '\n'.join(''.join(chr(code) for code in line) for line in lines)

<|endoftext|>


<commit_before>"""
The email responder plugin.  This plugin sends emails whenever a testrun is created / finished.
"""
__author__ = 'jcorbett'

import configparser
import logging
import json
from io import BytesIO
import time

from ..amqp import AMQPConnection

from slickqa import SlickConnection, EmailSystemConfiguration, Testrun, EmailSubscription, Result, ResultStatus, StoredFile, RunStatus, EmailOffSwitch
from slickqa import micromodels
from kombu import Consumer, Queue
from kombu.transport.base import Message
from jinja2 import Template
import cairosvg.surface

import pygal
from pygal.style import DefaultStyle, Style

# smtp imports
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.image import MIMEImage
from email.mime.base import MIMEBase
from email import encoders

class TestrunUpdateMessage(micromodels.Model):
    before = micromodels.ModelField(Testrun)
    after = micromodels.ModelField(Testrun)

class EmailResponder(object):
    """The email auto responder plugin"""

    default_subject_template = "{testrun.release.name} build {testrun.build.name} {testrun.name} Results for {testrun.project.name}"

    default_email_template = """

{{subject}}

            {% for status in testrun.summary.statusListOrdered %}
                {{status}}
                {{getattr(testrun.summary.resultsByStatus, status)}}

            {% endfor %}
                Total tests
                {{testrun.summary.total}}

WARNING: The results you are seeing are unreviewed.  The people in charge of this email are probably seeing the results at the same time as you.  Please refrain from accusing them or causing any form of bodily harm if you are not pleased with the results.

        {% if len(failed_results) > 0 %}

Failed Results

        {% for result in failed_results %}

{{result.testcase.name}}

{{result.reason}}

Files:
            {% for storedfile in result.files %}
                {{storedfile.filename}}
            {% endfor %}

        {% endfor %}
        {% endif %}

    """

    colors = {
        "PASS": "#008000",
        "FAIL": "#ff0000",
        "BROKEN_TEST": "#ffac00",
        "SKIPPED": "#bdb76b",
        "NOT_TESTED": "#1e90ff",
        "NO_RESULT": "#c0c0c0",
        "CANCELLED": "#a0522d"
    }

    def __init__(self, configuration, amqpcon, slick):
        assert(isinstance(configuration, configparser.ConfigParser))
        assert(isinstance(amqpcon, AMQPConnection))
        assert(isinstance(slick, SlickConnection))
        self.configuration = configuration
        self.amqpcon = amqpcon
        self.logger = logging.getLogger('narc.plugins.email.EmailResponder')
        self.logger.debug("Email Responder is gathering settings from slick.")
        self.slick = slick
        self.email_settings = self.slick.systemconfigurations(EmailSystemConfiguration).findOne()
        self.configured = False
        if self.email_settings is not None:
            assert(isinstance(self.email_settings, EmailSystemConfiguration))
            if self.email_settings.enabled:
                self.configured = True
                self.logger.debug("Recieved email settings: {}", self.email_settings.to_json())
            else:
                self.logger.info("Email responses have been disabled by email system configuration retrieved from slick.")

        if self.configured:
            self.channel = amqpcon.add_channel()
            self.queue = Queue('narc_testrun_email_response', exchange=amqpcon.exchange, routing_key='update.Testrun', durable=True)
            self.consumer = Consumer(self.channel, queues=[self.queue,], callbacks=[self.testrun_updated,])
            amqpcon.add_consumer(self.consumer)

    def testrun_updated(self, body, message):
        assert(isinstance(message, Message))
        if not message.acknowledged:
            message.ack()
        update = TestrunUpdateMessage.from_dict(body)
        self.logger.debug("Got update for testrun '{}', before state: '{}', after state: '{}'.", update.before.name, update.before.state, update.after.state)
        if update.before.state != RunStatus.FINISHED and update.after.state == RunStatus.FINISHED and self.should_email_for(update.after):
            to = self.get_addresses_for(update.after)
            if len(to) > 0:
                self.logger.info("Testrun with id {} and name {} just finished, generating email to subscribers: {}", update.after.id, update.after.name, ', '.join(to))
            else:
                self.logger.info("Testrun with id {} and name {} just finished, but no emails are subscribed.", update.after.id, update.after.name)
                return
            (subject_template, email_template) = self.get_templates_for(update.after)
            image_file_name = "chart-{}.png".format(str(int(time.time())))
            email_template = Template(email_template)
            subject = subject_template.format(testrun=update.after)

            #TODO: fix after creating results query
            failed_results = self.slick.results.find(testrunid=update.after.id, status=ResultStatus.FAIL)
            text = email_template.render(testrun=update.after, subject=subject, colors=EmailResponder.colors, image_file_name=image_file_name, url=self.url, failed_results=failed_results, getattr=getattr, len=len)
            image = self.generate_chart_for(update.after)
            self.mail(to, subject, text, image, image_file_name)

    def generate_chart_for(self, testrun):
        assert(isinstance(testrun, Testrun))
        colors = []
        for status in testrun.summary.statusListOrdered:
            colors.append(EmailResponder.colors[status])
        mystyle = Style(background='transparent',
                        plot_background='transparent',
                        colors=colors,
                        foreground='#d2d3d3',
                        foreground_light='#eee',
                        foreground_dark='#555')
        config = pygal.Config()
        config.show_legend = False
        pie_chart = pygal.Pie(config, width=400, height=300, style=mystyle)
        for status in testrun.summary.statusListOrdered:
            pie_chart.add(status, getattr(testrun.summary.resultsByStatus, status))
        fakefile_svg = BytesIO()
        fakefile_svg.write(pie_chart.render())
        fakefile_svg.seek(0)

        # this is what render_to_png does, but this way we don't have to write to a file
        return cairosvg.surface.PNGSurface.convert(file_obj=fakefile_svg)

    def mail(self, to, subject, text, image, image_file_name):
        # build a list from 'to'
        msg = MIMEMultipart()
        msg['From'] = self.email_settings.sender
        msg['To'] = ', '.join(to)
        msg['Subject'] = subject
        msg.attach(MIMEText(text, 'html'))

        img = MIMEImage(image)
        img.add_header('Content-ID', '<' + image_file_name + '>')
        msg.attach(img)

        mailServer = None
        if self.email_settings.ssl:
            mailServer = smtplib.SMTP_SSL(self.email_settings.smtpHostname, port=self.email_settings.smtpPort)
        else:
            mailServer = smtplib.SMTP(self.email_settings.smtpHostname, port=self.email_settings.smtpPort)
        mailServer.ehlo()
        if self.email_settings.smtpUsername is not None and self.email_settings.smtpUsername != "":
            mailServer.login(self.email_settings.smtpUsername, self.email_settings.smtpPassword)
        mailServer.sendmail(self.email_settings.sender, to, msg.as_string())
        # Should be mailServer.quit(), but that crashes...
        mailServer.close()

    def url(self, param, status=None):
        """Return a url based on the parameter passed in."""
        base_url = self.slick.baseUrl[0:-4]
        if isinstance(param, Testrun):
            if status is None:
                return "{}/#/reports/testrunsummary/{}".format(base_url, param.id)
            else:
                return "{}/#/reports/testrundetail/{}?only={}".format(base_url, param.id, status)
        elif isinstance(param, Result):
            return "{}/#/reports/result/{}".format(base_url, param.id)
        elif isinstance(param, StoredFile):
            return "{}/api/files/{}/content/{}".format(base_url, param.id, param.filename)

    def get_templates_for(self, testrun):
        assert(isinstance(testrun, Testrun))
        return (EmailResponder.default_subject_template, EmailResponder.default_email_template)

    def get_addresses_for(self, testrun):
        assert(isinstance(testrun, Testrun))
        email_addresses = self.slick.systemconfigurations(EmailSubscription).find()
        addresses = []
        for email_address in email_addresses:
            if email_address.enabled:
                for subscription in email_address.subscriptions:
                    if subscription.subscriptionType == 'Global':
                        addresses.append(email_address.name)
                    elif subscription.subscriptionType == 'Project' and testrun.project is not None and subscription.subscriptionValue == testrun.project.id:
                        addresses.append(email_address.name)
                    elif subscription.subscriptionType == 'Testplan' and testrun.testplan is not None and subscription.subscriptionValue == testrun.testplan.id:
                        addresses.append(email_address.name)
                    elif subscription.subscriptionType == 'Release' and testrun.release is not None and subscription.subscriptionValue == testrun.release.releaseId:
                        addresses.append(email_address.name)
                    elif subscription.subscriptionType == 'Configuration' and testrun.config is not None and subscription.subscriptionValue == testrun.config.configId:
                        addresses.append(email_address.name)
        return addresses

    def should_email_for(self, testrun):
        assert(isinstance(testrun, Testrun))
        off_switches = self.slick.systemconfigurations(EmailOffSwitch).find()
        for off_switch in off_switches:
            assert(isinstance(off_switch, EmailOffSwitch))
            if off_switch.turnOffEmailsForType == 'Global':
                self.logger.info("Found EmailOffSwitch (System Configuration id {}), with type Global, returning False for should_email_for.", off_switch.id)
                return False
            if off_switch.turnOffEmailsForType == 'Project' and testrun.project is not None and off_switch.turnOffEmailsForId == testrun.project.id:
                self.logger.info("Found EmailOffSwitch (System Configuration id {}) for Project with id {}, returning False for should_email_for.", off_switch.id, off_switch.turnOffEmailsForId)
                return False
            if off_switch.turnOffEmailsForType == 'Testplan' and testrun.testplan is not None and off_switch.turnOffEmailsForId == testrun.testplanid:
                self.logger.info("Found EmailOffSwitch (System Configuration id {}) for Testplan with id {}, returning False for should_email_for.", off_switch.id, off_switch.turnOffEmailsForId)
                return False
            if off_switch.turnOffEmailsForType == 'Release' and testrun.release is not None and off_switch.turnOffEmailsForId == testrun.release.releaseId:
                self.logger.info("Found EmailOffSwitch (System Configuration id {}) for Release with id {}, returning False for should_email_for.", off_switch.id, off_switch.turnOffEmailsForId)
                return False
            if off_switch.turnOffEmailsForType == 'Configuration' and testrun.config is not None and off_switch.turnOffEmailsForId == testrun.config.configId:
                self.logger.info("Found EmailOffSwitch (System Configuration id {}) for Configuration with id {}, returning False for should_email_for.", off_switch.id, off_switch.turnOffEmailsForId)
                return False
        return True

<commit_msg>changing the email plugin to point to urls for the new slick<commit_after>"""
The email responder plugin.  This plugin sends emails whenever a testrun is created / finished.
"""
__author__ = 'jcorbett'

import configparser
import logging
import json
from io import BytesIO
import time

from ..amqp import AMQPConnection

from slickqa import SlickConnection, EmailSystemConfiguration, Testrun, EmailSubscription, Result, ResultStatus, StoredFile, RunStatus, EmailOffSwitch
from slickqa import micromodels
from kombu import Consumer, Queue
from kombu.transport.base import Message
from jinja2 import Template
import cairosvg.surface

import pygal
from pygal.style import DefaultStyle, Style

# smtp imports
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.image import MIMEImage
from email.mime.base import MIMEBase
from email import encoders

class TestrunUpdateMessage(micromodels.Model):
    before = micromodels.ModelField(Testrun)
    after = micromodels.ModelField(Testrun)

class EmailResponder(object):
    """The email auto responder plugin"""

    default_subject_template = "{testrun.release.name} build {testrun.build.name} {testrun.name} Results for {testrun.project.name}"

    default_email_template = """

{{subject}}

            {% for status in testrun.summary.statusListOrdered %}
                {{status}}
                {{getattr(testrun.summary.resultsByStatus, status)}}

            {% endfor %}
                Total tests
                {{testrun.summary.total}}

WARNING: The results you are seeing are unreviewed.  The people in charge of this email are probably seeing the results at the same time as you.  Please refrain from accusing them or causing any form of bodily harm if you are not pleased with the results.

        {% if len(failed_results) > 0 %}

Failed Results

        {% for result in failed_results %}

{{result.testcase.name}}

{{result.reason}}

Files:
            {% for storedfile in result.files %}
                {{storedfile.filename}}
            {% endfor %}

        {% endfor %}
        {% endif %}

    """

    colors = {
        "PASS": "#008000",
        "FAIL": "#ff0000",
        "BROKEN_TEST": "#ffac00",
        "SKIPPED": "#bdb76b",
        "NOT_TESTED": "#1e90ff",
        "NO_RESULT": "#c0c0c0",
        "CANCELLED": "#a0522d"
    }

    def __init__(self, configuration, amqpcon, slick):
        assert(isinstance(configuration, configparser.ConfigParser))
        assert(isinstance(amqpcon, AMQPConnection))
        assert(isinstance(slick, SlickConnection))
        self.configuration = configuration
        self.amqpcon = amqpcon
        self.logger = logging.getLogger('narc.plugins.email.EmailResponder')
        self.logger.debug("Email Responder is gathering settings from slick.")
        self.slick = slick
        self.email_settings = self.slick.systemconfigurations(EmailSystemConfiguration).findOne()
        self.configured = False
        if self.email_settings is not None:
            assert(isinstance(self.email_settings, EmailSystemConfiguration))
            if self.email_settings.enabled:
                self.configured = True
                self.logger.debug("Recieved email settings: {}", self.email_settings.to_json())
            else:
                self.logger.info("Email responses have been disabled by email system configuration retrieved from slick.")

        if self.configured:
            self.channel = amqpcon.add_channel()
            self.queue = Queue('narc_testrun_email_response', exchange=amqpcon.exchange, routing_key='update.Testrun', durable=True)
            self.consumer = Consumer(self.channel, queues=[self.queue,], callbacks=[self.testrun_updated,])
            amqpcon.add_consumer(self.consumer)

    def testrun_updated(self, body, message):
        assert(isinstance(message, Message))
        if not message.acknowledged:
            message.ack()
        update = TestrunUpdateMessage.from_dict(body)
        self.logger.debug("Got update for testrun '{}', before state: '{}', after state: '{}'.", update.before.name, update.before.state, update.after.state)
        if update.before.state != RunStatus.FINISHED and update.after.state == RunStatus.FINISHED and self.should_email_for(update.after):
            to = self.get_addresses_for(update.after)
            if len(to) > 0:
                self.logger.info("Testrun with id {} and name {} just finished, generating email to subscribers: {}", update.after.id, update.after.name, ', '.join(to))
            else:
                self.logger.info("Testrun with id {} and name {} just finished, but no emails are subscribed.", update.after.id, update.after.name)
                return
            (subject_template, email_template) = self.get_templates_for(update.after)
            image_file_name = "chart-{}.png".format(str(int(time.time())))
            email_template = Template(email_template)
            subject = subject_template.format(testrun=update.after)

            #TODO: fix after creating results query
            failed_results = self.slick.results.find(testrunid=update.after.id, status=ResultStatus.FAIL)
            text = email_template.render(testrun=update.after, subject=subject, colors=EmailResponder.colors, image_file_name=image_file_name, url=self.url, failed_results=failed_results, getattr=getattr, len=len)
            image = self.generate_chart_for(update.after)
            self.mail(to, subject, text, image, image_file_name)

    def generate_chart_for(self, testrun):
        assert(isinstance(testrun, Testrun))
        colors = []
        for status in testrun.summary.statusListOrdered:
            colors.append(EmailResponder.colors[status])
        mystyle = Style(background='transparent',
                        plot_background='transparent',
                        colors=colors,
                        foreground='#d2d3d3',
                        foreground_light='#eee',
                        foreground_dark='#555')
        config = pygal.Config()
        config.show_legend = False
        pie_chart = pygal.Pie(config, width=400, height=300, style=mystyle)
        for status in testrun.summary.statusListOrdered:
            pie_chart.add(status, getattr(testrun.summary.resultsByStatus, status))
        fakefile_svg = BytesIO()
        fakefile_svg.write(pie_chart.render())
        fakefile_svg.seek(0)

        # this is what render_to_png does, but this way we don't have to write to a file
        return cairosvg.surface.PNGSurface.convert(file_obj=fakefile_svg)

    def mail(self, to, subject, text, image, image_file_name):
        # build a list from 'to'
        msg = MIMEMultipart()
        msg['From'] = self.email_settings.sender
        msg['To'] = ', '.join(to)
        msg['Subject'] = subject
        msg.attach(MIMEText(text, 'html'))

        img = MIMEImage(image)
        img.add_header('Content-ID', '<' + image_file_name + '>')
        msg.attach(img)

        mailServer = None
        if self.email_settings.ssl:
            mailServer = smtplib.SMTP_SSL(self.email_settings.smtpHostname, port=self.email_settings.smtpPort)
        else:
            mailServer = smtplib.SMTP(self.email_settings.smtpHostname, port=self.email_settings.smtpPort)
        mailServer.ehlo()
        if self.email_settings.smtpUsername is not None and self.email_settings.smtpUsername != "":
            mailServer.login(self.email_settings.smtpUsername, self.email_settings.smtpPassword)
        mailServer.sendmail(self.email_settings.sender, to, msg.as_string())
        # Should be mailServer.quit(), but that crashes...
        mailServer.close()

    def url(self, param, status=None, testrun=None):
        """Return a url based on the parameter passed in."""
        base_url = self.slick.baseUrl[0:-4]
        if isinstance(param, Testrun):
            if status is None:
                return "{}/testruns/{}".format(base_url, param.id)
            else:
                return "{}/testruns/{}?only={}".format(base_url, param.id, status)
        elif isinstance(param, Result):
            return "{}/#/testruns/{}?result={}".format(base_url, testrun.id, param.id)
        elif isinstance(param, StoredFile):
            return "{}/api/files/{}/content/{}".format(base_url, param.id, param.filename)

    def get_templates_for(self, testrun):
        assert(isinstance(testrun, Testrun))
        return (EmailResponder.default_subject_template, EmailResponder.default_email_template)

    def get_addresses_for(self, testrun):
        assert(isinstance(testrun, Testrun))
        email_addresses = self.slick.systemconfigurations(EmailSubscription).find()
        addresses = []
        for email_address in email_addresses:
            if email_address.enabled:
                for subscription in email_address.subscriptions:
                    if subscription.subscriptionType == 'Global':
                        addresses.append(email_address.name)
                    elif subscription.subscriptionType == 'Project' and testrun.project is not None and subscription.subscriptionValue == testrun.project.id:
                        addresses.append(email_address.name)
                    elif subscription.subscriptionType == 'Testplan' and testrun.testplan is not None and subscription.subscriptionValue == testrun.testplan.id:
                        addresses.append(email_address.name)
                    elif subscription.subscriptionType == 'Release' and testrun.release is not None and subscription.subscriptionValue == testrun.release.releaseId:
                        addresses.append(email_address.name)
                    elif subscription.subscriptionType == 'Configuration' and testrun.config is not None and subscription.subscriptionValue == testrun.config.configId:
                        addresses.append(email_address.name)
        return addresses

    def should_email_for(self, testrun):
        assert(isinstance(testrun, Testrun))
        off_switches = self.slick.systemconfigurations(EmailOffSwitch).find()
        for off_switch in off_switches:
            assert(isinstance(off_switch, EmailOffSwitch))
            if off_switch.turnOffEmailsForType == 'Global':
                self.logger.info("Found EmailOffSwitch (System Configuration id {}), with type Global, returning False for should_email_for.", off_switch.id)
                return False
            if off_switch.turnOffEmailsForType == 'Project' and testrun.project is not None and off_switch.turnOffEmailsForId == testrun.project.id:
                self.logger.info("Found EmailOffSwitch (System Configuration id {}) for Project with id {}, returning False for should_email_for.", off_switch.id, off_switch.turnOffEmailsForId)
                return False
            if off_switch.turnOffEmailsForType == 'Testplan' and testrun.testplan is not None and off_switch.turnOffEmailsForId == testrun.testplanid:
                self.logger.info("Found EmailOffSwitch (System Configuration id {}) for Testplan with id {}, returning False for should_email_for.", off_switch.id, off_switch.turnOffEmailsForId)
                return False
            if off_switch.turnOffEmailsForType == 'Release' and testrun.release is not None and off_switch.turnOffEmailsForId == testrun.release.releaseId:
                self.logger.info("Found EmailOffSwitch (System Configuration id {}) for Release with id {}, returning False for should_email_for.", off_switch.id, off_switch.turnOffEmailsForId)
                return False
            if off_switch.turnOffEmailsForType == 'Configuration' and testrun.config is not None and off_switch.turnOffEmailsForId == testrun.config.configId:
                self.logger.info("Found EmailOffSwitch (System Configuration id {}) for Configuration with id {}, returning False for should_email_for.", off_switch.id, off_switch.turnOffEmailsForId)
                return False
        return True

<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
from __future__ import absolute_import, unicode_literals
from django.utils.html import format_html
from .database_maintenance_task import DatabaseMaintenanceTaskAdmin

class AddInstancesToDatabaseAdmin(DatabaseMaintenanceTaskAdmin):

    list_filter = [
        "database__team", "current_step", "number_of_instances", "status",
        "number_of_instances_before"
    ]

    list_display = (
        "database", "database_team", "number_of_instances", "number_of_instances_before",
        "current_step", "friendly_status", "maintenance_action", "link_task",
        "started_at", "finished_at"
    )

    readonly_fields = (
        "database", "number_of_instances", "number_of_instances_before",
        "link_task", "started_at", "finished_at", "current_step", "status",
        "maintenance_action"
    )

    def maintenance_action(self, maintenance):
        if not maintenance.is_status_error or not maintenance.can_do_retry:
            return 'N/A'

        url_retry = maintenance.database.get_resize_retry_url()
        html_retry = ("Retry").format(url_retry)

        url_rollback = maintenance.database.get_resize_rollback_url()
        html_rollback = ("Rollback").format(url_rollback)

        spaces = '&nbsp' * 3
        html_content = '{}{}{}'.format(html_rollback, spaces, html_retry)
        return format_html(html_content)
<commit_msg>Adding 'add_instances_database' url actions to AddInstancesToDatabaseAdmin<commit_after># -*- coding: utf-8 -*-
from __future__ import absolute_import, unicode_literals
from django.utils.html import format_html
from .database_maintenance_task import DatabaseMaintenanceTaskAdmin

class AddInstancesToDatabaseAdmin(DatabaseMaintenanceTaskAdmin):

    list_filter = [
        "database__team", "current_step", "number_of_instances", "status",
        "number_of_instances_before"
    ]

    list_display = (
        "database", "database_team", "number_of_instances", "number_of_instances_before",
        "current_step", "friendly_status", "maintenance_action", "link_task",
        "started_at", "finished_at"
    )

    readonly_fields = (
        "database", "number_of_instances", "number_of_instances_before",
        "link_task", "started_at", "finished_at", "current_step", "status",
        "maintenance_action"
    )

    def maintenance_action(self, maintenance):
        if not maintenance.is_status_error or not maintenance.can_do_retry:
            return 'N/A'

        url_retry = maintenance.database.get_add_instances_database_retry_url()
        html_retry = ("Retry").format(url_retry)

        url_rollback = maintenance.database.get_add_instances_database_rollback_url()
        html_rollback = ("Rollback").format(url_rollback)

        spaces = '&nbsp' * 3
        html_content = '{}{}{}'.format(html_rollback, spaces, html_retry)
        return format_html(html_content)
<|endoftext|>


<commit_before>import json
import logging
import os
import shutil
import sys
import webbrowser

from ctypes import windll
from functools import wraps
from uuid import uuid1
from threading import Event
from cefpython3 import cefpython as cef
from copy import copy
from time import sleep

from webview.js.css import disable_text_select
from webview.js import dom
from webview import _debug, _user_agent
from webview.util import parse_api_js, default_html, js_bridge_call
from webview.window import FixPoint

sys.excepthook = cef.ExceptHook
instances = {}

logger = logging.getLogger(__name__)

settings = {}

command_line_switches = {}

def _set_dpi_mode(enabled):
    """
    """
    try:
        import _winreg as winreg  # Python 2
    except ImportError:
        import winreg  # Python 3

    try:
        dpi_support = winreg.OpenKey(winreg.HKEY_CURRENT_USER,
                                     r'Software\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers',
                                     0, winreg.KEY_ALL_ACCESS)
    except WindowsError:
        dpi_support = winreg.CreateKeyEx(winreg.HKEY_CURRENT_USER,
                                         r'Software\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers',
                                         0, winreg.KEY_ALL_ACCESS)

    try:
        subprocess_path = os.path.join(sys._MEIPASS, 'subprocess.exe')
    except:
        subprocess_path = os.path.join(os.path.dirname(cef.__file__), 'subprocess.exe')

    if enabled:
        winreg.SetValueEx(dpi_support, subprocess_path, 0, winreg.REG_SZ, '~HIGHDPIAWARE')
    else:
        winreg.DeleteValue(dpi_support, subprocess_path)

    winreg.CloseKey(dpi_support)

class JSBridge:
    def __init__(self, window, eval_events):
        self.results = {}
        self.window = window
        self.eval_events = eval_events

    def return_result(self, result, uid):
        self.results[uid] = json.loads(result) if result else None
        self.eval_events[uid].set()

    def call(self, func_name, param, value_id):
        js_bridge_call(self.window, func_name, json.loads(param), value_id)

renderer = 'cef'

class Browser:
    def __init__(self, window, handle, browser):
        self.window = window
        self.handle = handle
        self.browser = browser
        self.text_select = window.text_select
        self.uid = window.uid
        self.loaded = window.loaded
        self.shown = window.shown
        self.inner_hwnd = self.browser.GetWindowHandle()
        self.eval_events = {}
        self.js_bridge = JSBridge(window, self.eval_events)
        self.initialized = False

    def initialize(self):
        if self.initialized:
            return

        self.browser.GetJavascriptBindings().Rebind()
        self.browser.ExecuteJavascript(parse_api_js(self.window, 'cef'))

        if not self.text_select:
            self.browser.ExecuteJavascript(disable_text_select)

        self.browser.ExecuteJavascript(dom.src)

        sleep(0.1)  # wait for window.pywebview to load
        self.initialized = True
        self.loaded.set()

    def close(self):
        self.browser.CloseBrowser(True)

    def resize(self, width, height, fix_point):
        windll.user32.SetWindowPos(self.inner_hwnd, 0, 0, 0, width - 16, height - 38,
                                   0x0002 | 0x0004 | 0x0010)
        self.browser.NotifyMoveOrResizeStarted()

    def evaluate_js(self, code):
        self.loaded.wait()
        eval_script = """
            try {{
                window.external.return_result({0}, '{1}');
            }} catch(e) {{
                console.error(e.stack);
                window.external.return_result(null, '{1}');
            }}
        """

        id_ = uuid1().hex[:8]
        self.eval_events[id_] = Event()
        self.browser.ExecuteJavascript(eval_script.format(code, id_))
        self.eval_events[id_].wait()  # result is obtained via JSBridge.return_result

        result = copy(self.js_bridge.results[id_])

        del self.eval_events[id_]
        del self.js_bridge.results[id_]

        return result

    def get_current_url(self):
        self.loaded.wait()
        return self.browser.GetUrl()

    def load_url(self, url):
        self.initialized = False
        self.loaded.clear()
        self.browser.LoadUrl(url)

    def load_html(self, html):
        self.initialized = False
        self.loaded.clear()
        self.browser.LoadUrl('data:text/html,{0}'.format(html))

def find_instance(browser):
    for instance in instances.values():
        if instance.browser is browser:
            return instance

    return None

class LoadHandler(object):
    def OnBeforePopup(self, **args):
        url = args['target_url']
        user_gesture = args['user_gesture']

        if user_gesture:
            webbrowser.open(url)

        return True

    def OnLoadingStateChange(self, browser, is_loading, **_):
        instance = find_instance(browser)

        if instance is not None:
            if is_loading:
                instance.initialized = False
            else:
                instance.initialize()
        else:
            logger.debug('CEF instance is not found %s ' % browser)

def _cef_call(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        uid = args[-1]

        if uid not in instances:
            logger.debug('CEF window with uid {0} does not exist'.format(uid))
            return

        return func(*args, **kwargs)

    return wrapper

def init(window):
    global _initialized

    if not _initialized:
        if sys.platform == 'win32':
            _set_dpi_mode(True)

        default_settings = {
            'multi_threaded_message_loop': True,
            'context_menu': {
                'enabled': _debug['mode']
            }
        }

        default_command_line_switches = {
            "enable-media-stream": ""
        }

        if not _debug['mode']:
            default_settings['remote_debugging_port'] = -1

        if _user_agent:
            default_settings['user_agent'] = _user_agent

        try:  # set paths under Pyinstaller's one file mode
            default_settings.update({
                'resources_dir_path': sys._MEIPASS,
                'locales_dir_path': os.path.join(sys._MEIPASS, 'locales'),
                'browser_subprocess_path': os.path.join(sys._MEIPASS, 'subprocess.exe'),
            })
        except Exception:
            pass

        all_settings = dict(default_settings, **settings)
        all_command_line_switches = dict(default_command_line_switches, **command_line_switches)
        cef.Initialize(settings=all_settings, commandLineSwitches=all_command_line_switches)
        cef.DpiAware.EnableHighDpiSupport()

        _initialized = True

def create_browser(window, handle, alert_func):
    def _create():
        real_url = 'data:text/html,{0}'.format(window.html) if window.html else window.real_url or 'data:text/html,{0}'.format(default_html)
        cef_browser = cef.CreateBrowserSync(window_info=window_info, url=real_url)
        browser = Browser(window, handle, cef_browser)

        bindings = cef.JavascriptBindings()
        bindings.SetObject('external', browser.js_bridge)
        bindings.SetFunction('alert', alert_func)

        cef_browser.SetJavascriptBindings(bindings)
        cef_browser.SetClientHandler(LoadHandler())

        instances[window.uid] = browser
        window.shown.set()

    window_info = cef.WindowInfo()
    window_info.SetAsChild(handle)
    cef.PostTask(cef.TID_UI, _create)

@_cef_call
def load_html(html, uid):
    instance = instances[uid]
    instance.load_html(html)

@_cef_call
def load_url(url, uid):
    instance = instances[uid]
    instance.load_url(url)

@_cef_call
def evaluate_js(code, uid):
    instance = instances[uid]
    return instance.evaluate_js(code)

@_cef_call
def get_current_url(uid):
    instance = instances[uid]
    url = instance.get_current_url()

    if url.startswith('data:text/html,'):
        return None
    else:
        return url

@_cef_call
def resize(width, height, uid, fix_point):
    instance = instances[uid]
    instance.resize(width, height, fix_point)

@_cef_call
def close_window(uid):
    instance = instances[uid]
    instance.close()
    del instances[uid]

def shutdown():
    try:
        if os.path.exists('blob_storage'):
            shutil.rmtree('blob_storage')

        if os.path.exists('webrtc_event_logs'):
            shutil.rmtree('webrtc_event_logs')

        if os.path.exists('error.log'):
            os.remove('error.log')

        if sys.platform == 'win32':
            _set_dpi_mode(False)

    except Exception as e:
        logger.debug(e, exc_info=True)

    cef.Shutdown()

_initialized = False
<commit_msg>Fix CEF resize<commit_after>import json
import logging
import os
import shutil
import sys
import webbrowser

from ctypes import windll
from functools import wraps
from uuid import uuid1
from threading import Event
from cefpython3 import cefpython as cef
from copy import copy
from time import sleep

from webview.js.css import disable_text_select
from webview.js import dom
from webview import _debug, _user_agent
from webview.util import parse_api_js, default_html, js_bridge_call
from webview.window import FixPoint

sys.excepthook = cef.ExceptHook
instances = {}

logger = logging.getLogger(__name__)

settings = {}

command_line_switches = {}

def _set_dpi_mode(enabled):
    """
    """
    try:
        import _winreg as winreg  # Python 2
    except ImportError:
        import winreg  # Python 3

    try:
        dpi_support = winreg.OpenKey(winreg.HKEY_CURRENT_USER,
                                     r'Software\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers',
                                     0, winreg.KEY_ALL_ACCESS)
    except WindowsError:
        dpi_support = winreg.CreateKeyEx(winreg.HKEY_CURRENT_USER,
                                         r'Software\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers',
                                         0, winreg.KEY_ALL_ACCESS)

    try:
        subprocess_path = os.path.join(sys._MEIPASS, 'subprocess.exe')
    except:
        subprocess_path = os.path.join(os.path.dirname(cef.__file__), 'subprocess.exe')

    if enabled:
        winreg.SetValueEx(dpi_support, subprocess_path, 0, winreg.REG_SZ, '~HIGHDPIAWARE')
    else:
        winreg.DeleteValue(dpi_support, subprocess_path)

    winreg.CloseKey(dpi_support)

class JSBridge:
    def __init__(self, window, eval_events):
        self.results = {}
        self.window = window
        self.eval_events = eval_events

    def return_result(self, result, uid):
        self.results[uid] = json.loads(result) if result else None
        self.eval_events[uid].set()

    def call(self, func_name, param, value_id):
        js_bridge_call(self.window, func_name, json.loads(param), value_id)

renderer = 'cef'

class Browser:
    def __init__(self, window, handle, browser):
        self.window = window
        self.handle = handle
        self.browser = browser
        self.text_select = window.text_select
        self.uid = window.uid
        self.loaded = window.loaded
        self.shown = window.shown
        self.inner_hwnd = self.browser.GetWindowHandle()
        self.eval_events = {}
        self.js_bridge = JSBridge(window, self.eval_events)
        self.initialized = False

    def initialize(self):
        if self.initialized:
            return

        self.browser.GetJavascriptBindings().Rebind()
        self.browser.ExecuteJavascript(parse_api_js(self.window, 'cef'))

        if not self.text_select:
            self.browser.ExecuteJavascript(disable_text_select)

        self.browser.ExecuteJavascript(dom.src)

        sleep(0.1)  # wait for window.pywebview to load
        self.initialized = True
        self.loaded.set()

    def close(self):
        self.browser.CloseBrowser(True)

    def resize(self, width, height):
        windll.user32.SetWindowPos(self.inner_hwnd, 0, 0, 0, width - 16, height - 38,
                                   0x0002 | 0x0004 | 0x0010)
        self.browser.NotifyMoveOrResizeStarted()

    def evaluate_js(self, code):
        self.loaded.wait()
        eval_script = """
            try {{
                window.external.return_result({0}, '{1}');
            }} catch(e) {{
                console.error(e.stack);
                window.external.return_result(null, '{1}');
            }}
        """

        id_ = uuid1().hex[:8]
        self.eval_events[id_] = Event()
        self.browser.ExecuteJavascript(eval_script.format(code, id_))
        self.eval_events[id_].wait()  # result is obtained via JSBridge.return_result

        result = copy(self.js_bridge.results[id_])

        del self.eval_events[id_]
        del self.js_bridge.results[id_]

        return result

    def get_current_url(self):
        self.loaded.wait()
        return self.browser.GetUrl()

    def load_url(self, url):
        self.initialized = False
        self.loaded.clear()
        self.browser.LoadUrl(url)

    def load_html(self, html):
        self.initialized = False
        self.loaded.clear()
        self.browser.LoadUrl('data:text/html,{0}'.format(html))

def find_instance(browser):
    for instance in instances.values():
        if instance.browser is browser:
            return instance

    return None

class LoadHandler(object):
    def OnBeforePopup(self, **args):
        url = args['target_url']
        user_gesture = args['user_gesture']

        if user_gesture:
            webbrowser.open(url)

        return True

    def OnLoadingStateChange(self, browser, is_loading, **_):
        instance = find_instance(browser)

        if instance is not None:
            if is_loading:
                instance.initialized = False
            else:
                instance.initialize()
        else:
            logger.debug('CEF instance is not found %s ' % browser)

def _cef_call(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        uid = args[-1]

        if uid not in instances:
            logger.debug('CEF window with uid {0} does not exist'.format(uid))
            return

        return func(*args, **kwargs)

    return wrapper

def init(window):
    global _initialized

    if not _initialized:
        if sys.platform == 'win32':
            _set_dpi_mode(True)

        default_settings = {
            'multi_threaded_message_loop': True,
            'context_menu': {
                'enabled': _debug['mode']
            }
        }

        default_command_line_switches = {
            "enable-media-stream": ""
        }

        if not _debug['mode']:
            default_settings['remote_debugging_port'] = -1

        if _user_agent:
            default_settings['user_agent'] = _user_agent

        try:  # set paths under Pyinstaller's one file mode
            default_settings.update({
                'resources_dir_path': sys._MEIPASS,
                'locales_dir_path': os.path.join(sys._MEIPASS, 'locales'),
                'browser_subprocess_path': os.path.join(sys._MEIPASS, 'subprocess.exe'),
            })
        except Exception:
            pass

        all_settings = dict(default_settings, **settings)
        all_command_line_switches = dict(default_command_line_switches, **command_line_switches)
        cef.Initialize(settings=all_settings, commandLineSwitches=all_command_line_switches)
        cef.DpiAware.EnableHighDpiSupport()

        _initialized = True

def create_browser(window, handle, alert_func):
    def _create():
        real_url = 'data:text/html,{0}'.format(window.html) if window.html else window.real_url or 'data:text/html,{0}'.format(default_html)
        cef_browser = cef.CreateBrowserSync(window_info=window_info, url=real_url)
        browser = Browser(window, handle, cef_browser)

        bindings = cef.JavascriptBindings()
        bindings.SetObject('external', browser.js_bridge)
        bindings.SetFunction('alert', alert_func)

        cef_browser.SetJavascriptBindings(bindings)
        cef_browser.SetClientHandler(LoadHandler())

        instances[window.uid] = browser
        window.shown.set()

    window_info = cef.WindowInfo()
    window_info.SetAsChild(handle)
    cef.PostTask(cef.TID_UI, _create)

@_cef_call
def load_html(html, uid):
    instance = instances[uid]
    instance.load_html(html)

@_cef_call
def load_url(url, uid):
    instance = instances[uid]
    instance.load_url(url)

@_cef_call
def evaluate_js(code, uid):
    instance = instances[uid]
    return instance.evaluate_js(code)

@_cef_call
def get_current_url(uid):
    instance = instances[uid]
    url = instance.get_current_url()

    if url.startswith('data:text/html,'):
        return None
    else:
        return url

@_cef_call
def resize(width, height, uid):
    instance = instances[uid]
    instance.resize(width, height)

@_cef_call
def close_window(uid):
    instance = instances[uid]
    instance.close()
    del instances[uid]

def shutdown():
    try:
        if os.path.exists('blob_storage'):
            shutil.rmtree('blob_storage')

        if os.path.exists('webrtc_event_logs'):
            shutil.rmtree('webrtc_event_logs')

        if os.path.exists('error.log'):
            os.remove('error.log')

        if sys.platform == 'win32':
            _set_dpi_mode(False)

    except Exception as e:
        logger.debug(e, exc_info=True)

    cef.Shutdown()

_initialized = False
<|endoftext|>