def _start(self):
        """Start threads and check for inactive workers."""
        if self._queue_workers and self._running_threads < self.MAX_THREADS:
            # print('Queue: {0} Running: {1} Workers: {2} '
            #       'Threads: {3}'.format(len(self._queue_workers),
            #                                 self._running_threads,
            #                                 len(self._workers),
            #                                 len(self._threads)))
            self._running_threads += 1
            thread = QThread()
            worker = self._queue_workers.popleft()
            worker.moveToThread(thread)
            worker.sig_finished.connect(thread.quit)
            thread.started.connect(worker.start)
            thread.start()
            self._threads.append(thread)

        if self._workers:
            for w in self._workers:
                if w.is_finished():
                    self._bag_collector.append(w)
                    self._workers.remove(w)

        if self._threads:
            for t in self._threads:
                if t.isFinished():
                    self._threads.remove(t)
                    self._running_threads -= 1

        if len(self._threads) == 0 and len(self._workers) == 0:
            self._timer.stop()
            self._timer_worker_delete.start()
Beispiel #2
0
    def shutdown(self, shutdown_kernel=True):
        """Shutdown connection and kernel."""
        if self.shutting_down:
            return
        self.shutting_down = True
        if shutdown_kernel:
            if not self.kernel_manager:
                return

            self.interrupt_kernel()
            if self.kernel_manager:
                self.kernel_manager.stop_restarter()
            self.spyder_kernel_comm.close()
            if self.kernel_client is not None:
                self.kernel_client.stop_channels()
            if self.kernel_manager:
                shutdown_thread = QThread(None)
                shutdown_thread.kernel_manager = self.kernel_manager
                shutdown_thread.run = self.shutdown_kernel
                self.shutdown_thread_list.append(shutdown_thread)
                shutdown_thread.start()
        else:
            self.spyder_kernel_comm.close(shutdown_channel=False)
            if self.kernel_client is not None:
                self.kernel_client.stop_channels()

        self.prune_shutdown_thread_list()
        super(ShellWidget, self).shutdown()
Beispiel #3
0
class PMQThreadManager(QObject):
    signal_server_message_received = Signal(dict)
    signal_data_changed = Signal(str)

    def __init__(self,
                 parent=None,
                 work_fcn: Callable = None,
                 loop_fcn: Callable = None,
                 exit_fcn: Callable = None):
        super().__init__(parent)
        self.thread_recv = QThread()
        self.worker_recv = PMGThreadWorker()

        if work_fcn is not None and loop_fcn is not None:
            raise ValueError(
                'work_fcn and loop_fcn cannot be both not None at the same time!'
            )
        if work_fcn is not None:
            self.worker_recv.work = work_fcn
        else:
            self.worker_recv.work_loop_fcn = loop_fcn
        self.worker_recv.moveToThread(self.thread_recv)
        self.thread_recv.started.connect(
            self.worker_recv.work)  # self.worker_recv.work)
        self.worker_recv.on_exit_fcn = exit_fcn
        self.thread_recv.start()

    def shut_down(self):
        logger.info('client quit')
        self.worker_recv.on_exit()
        # self.worker_recv.close_socket()
        self.thread_recv.quit()
        self.thread_recv.wait(500)
Beispiel #4
0
    def on_incoming_connection(self, socket_descriptor: int) -> None:
        """Create new client thread.

        Args:
            socket_descriptor (int): Socket descriptor.
        """
        client_id = self.get_free_id()

        thread = QThread()
        thread.setObjectName(str(client_id))
        client = _SocketClient(socket_descriptor, client_id)
        client.moveToThread(thread)
        thread.started.connect(client.run)  # noqa

        client.connected.connect(self.connected.emit)
        client.message.connect(self.message.emit)
        client.error.connect(self.error.emit)
        client.disconnected.connect(self.on_client_disconnected)

        client.disconnected.connect(thread.quit)
        client.disconnected.connect(thread.wait)
        thread.finished.connect(self.on_thread_finished)

        self.clients.append(client)
        self.threads.append(thread)
        thread.start()

        self._logger.info("Started new client thread!")
        self._logger.debug("Active clients: {}".format(len([x for x in self.threads if x.isRunning()])))
Beispiel #5
0
class DamTerm(QObject): #Main Class
    def __init__(self, addr):
        super(self.__class__, self).__init__()
        self.start()

    @pyqtSlot(str)
    def take_info(self, _str):
        print("TAKE::INFO:: %s" % _str)
        with open("test", "w") as f:
            f.write("Test")
        self.info.write(_str)

    def test(self):
        print("TESTETT!")


    def start(self):
        print("Start!")
        self.GenUids = GenerateUids(self)
        self.th = QThread(self)
        self.th.setObjectName("GenerateUids")
        self.GenUids.moveToThread(self.th)
        self.GenUids.pasInfo.connect(self.take_info)
        self.th.started.connect(self.GenUids.run)
        self.th.start()
        while not self.th.isFinished():
            time.sleep(1)
            print("while")
        print("Koniec startu")
Beispiel #6
0
class Main(QMainWindow):
    def __init__(self, tracker: Tracker) -> None:
        super().__init__()
        self.qttracker = QtTracker(tracker)
        self.bg_thread = QThread(self)
        self.qttracker.moveToThread(self.bg_thread)
        self.bg_thread.start()
        self.qttracker.start_recording()

        self.setCentralWidget(Controls(self.qttracker))
        toolbar = QToolBar()
        self.addToolBar(toolbar)
        toolbar.setMovable(False)
        toolbar.setToolButtonStyle(Qt.ToolButtonTextUnderIcon)
        self.new_action = QAction("New", self)
        self.new_action.setToolTip("Create a new file for tracking.")
        icon = self.style().standardIcon(QStyle.SP_FileIcon)
        self.new_action.setIcon(icon)
        self.new_action.triggered.connect(self.new_db)
        toolbar.addAction(self.new_action)

        self.load_action = QAction("Load", self)
        self.load_action.setToolTip("Load a existing file for tracking.")
        icon = self.style().standardIcon(QStyle.SP_DialogOpenButton)
        self.load_action.setIcon(icon)
        self.load_action.triggered.connect(self.load_db)
        toolbar.addAction(self.load_action)

    @Slot()
    def load_db(self):
        fname, ftype = QFileDialog.getOpenFileName(
            self,
            "Tracking files",
            str(Path.home()),
            "CamTrack Files (*.camtrack)",
            options=QFileDialog.DontUseNativeDialog)
        if fname != "":
            self.qttracker.tracker.open_db(fname)

    @Slot()
    def new_db(self):
        fname, ftype = QFileDialog.getSaveFileName(
            self,
            "New file",
            str(Path.home()),
            "CamTrack Files (*.camtrack)",
            options=QFileDialog.DontUseNativeDialog)
        if fname != 0:
            if (p := Path(fname).with_suffix('.camtrack')).exists():
                p.unlink()
            self.qttracker.tracker.open_db(fname + '.camtrack')
    def run(self):
        """
        运行代码
        """
        call_id_list = self.scene.topo_sort()
        self.scene.call_id_list = call_id_list

        thread = QThread()
        for node in self.scene.nodes:
            node.reset()
            node.content.moveToThread(thread)
        worker = self.scene.find_node(call_id_list[0]).content
        worker.moveToThread(thread)
        thread.started.connect(worker._process)
        thread.start()
        self.worker = worker
        self.thread = thread
Beispiel #8
0
    def create_worker(self):
        """Creates new socket worker in thread."""
        thread = QThread()
        worker = _SocketWorker()
        worker.moveToThread(thread)

        worker.connected.connect(self.connected.emit)  # noqa
        worker.message.connect(self.message.emit)  # noqa
        worker.disconnected.connect(self.disconnected.emit)  # noqa
        worker.error.connect(self.error.emit)  # noqa

        thread.started.connect(worker.start)  # noqa
        worker.closed.connect(thread.quit)  # noqa
        worker.closed.connect(thread.wait)  # noqa

        self.workers.append(worker)
        self.threads.append(thread)
        thread.start()
Beispiel #9
0
class PMClient(QObject, BaseClient):
    signal_server_message_received = Signal(dict)
    signal_data_changed = Signal(str)

    def __init__(self, parent=None, name='Anonymous QtClient'):
        super().__init__(parent)
        self.name = name
        self.client = self.init_socket(12306)
        self.thread_recv = QThread()
        self.worker_recv = RecvWork(self.client, self.name)
        self.signal_received = self.worker_recv.signal_received
        self.worker_recv.signal_received.connect(self.on_server_message_received)

        self.worker_recv.moveToThread(self.thread_recv)
        self.thread_recv.started.connect(self.worker_recv.work)
        self.thread_recv.start()

    def shut_down(self):
        logger.info('client quit')
        self.worker_recv.close_socket()
        self.thread_recv.quit()
        self.thread_recv.wait(500)

    def on_server_message_received(self, packet: bytes):
        try:
            dic = json.loads(packet)
            logger.info(dic)
            msg = dic.get('message')
            if msg == 'data_changed':
                data_name = dic.get('data_name')
                if data_name is not None:
                    self.signal_data_changed.emit(data_name)
            self.signal_server_message_received.emit(dic)
        except:
            import traceback
            traceback.print_exc()
            pass

    def send_bytes(self, packet: bytes):
        self.client.sendall(packet)

    @timeit
    def compress(self, byte_str):
        return zlib.compress(byte_str)
Beispiel #10
0
class PMGServer(QObject):
    def __init__(self, address: Tuple[str, int], parent=None):
        super().__init__(parent)
        self.dispatcher_dic = {}
        self.long_conn_sockets: Dict[str, socket.socket] = {}
        self.socket = init_socket(address)
        self.server_loop_thread = QThread()
        self.loop_worker = LoopWork(self, self.socket)
        self.loop_worker.moveToThread(self.server_loop_thread)

        self.server_loop_thread.started.connect(self.loop_worker.work)
        self.server_loop_thread.start()

    def broadcast_message(self, message_dic: dict = None):
        """
        广播信息
        message:传递信息
        # 'DATA_CHANGED'
        # 'SHUT_DOWN'
        :param message_dic:要发送的信息,应该是一个可以json序列化的字典。
        :return:
        """

        if message_dic is None:
            message_dic = {'name': 'broadcast', 'message': 'Are you alive?'}
        ids = []
        logger.info('broadcast message:' + repr(message_dic))
        for k in self.long_conn_sockets.keys():
            try:
                self.long_conn_sockets[k].sendall(
                    json.dumps(message_dic).encode('utf8') + b'PMEND')
            except ConnectionResetError:
                ids.append(k)
                logger.info('Connection \'%s\' closed!' % k)
            except:
                import traceback
                traceback.print_exc()
                ids.append(k)
        logger.info('died connections:' + repr(ids))
        for not_used_socket_name in ids:
            sock = self.long_conn_sockets.pop(not_used_socket_name)
            sock.close()
Beispiel #11
0
    def _start(self, worker=None):
        """Start threads and check for inactive workers."""
        if worker:
            self._queue_workers.append(worker)

        if self._queue_workers and self._running_threads < self._max_threads:
            #print('Queue: {0} Running: {1} Workers: {2} '
            #       'Threads: {3}'.format(len(self._queue_workers),
            #                                 self._running_threads,
            #                                 len(self._workers),
            #                                 len(self._threads)))
            self._running_threads += 1
            worker = self._queue_workers.popleft()
            thread = QThread(None)
            if isinstance(worker, PythonWorker):
                worker.moveToThread(thread)
                worker.sig_finished.connect(thread.quit)
                thread.started.connect(worker._start)
                thread.start()
            elif isinstance(worker, ProcessWorker):
                thread.quit()
                thread.wait()
                worker._start()
            self._threads.append(thread)
        else:
            self._timer.start()

        if self._workers:
            for w in self._workers:
                if w.is_finished():
                    self._bag_collector.append(w)
                    self._workers.remove(w)

        if self._threads:
            for t in self._threads:
                if t.isFinished():
                    self._threads.remove(t)
                    self._running_threads -= 1

        if len(self._threads) == 0 and len(self._workers) == 0:
            self._timer.stop()
            self._timer_worker_delete.start()
Beispiel #12
0
class PMQThreadObject(QObject):
    signal_server_message_received = Signal(dict)
    signal_data_changed = Signal(str)

    def __init__(self, parent=None, worker: QObject = None):
        super().__init__(parent)
        self.thread = QThread()
        self.worker = worker
        self.worker.moveToThread(self.thread)
        self.thread.started.connect(self.worker.work)  # self.worker_recv.work)
        self.thread.finished.connect(self.worker.deleteLater)
        self.thread.finished.connect(self.thread.deleteLater)
        self.thread.start()

    def terminate(self):
        logger.info('client quit')
        self.worker.on_exit()

        self.thread.quit()
        self.thread.wait(500)
Beispiel #13
0
    def _start(self, worker=None):
        """Start threads and check for inactive workers."""
        if worker:
            self._queue_workers.append(worker)

        if self._queue_workers and self._running_threads < self._max_threads:
            #print('Queue: {0} Running: {1} Workers: {2} '
            #       'Threads: {3}'.format(len(self._queue_workers),
            #                                 self._running_threads,
            #                                 len(self._workers),
            #                                 len(self._threads)))
            self._running_threads += 1
            worker = self._queue_workers.popleft()
            thread = QThread()
            if isinstance(worker, PythonWorker):
                worker.moveToThread(thread)
                worker.sig_finished.connect(thread.quit)
                thread.started.connect(worker._start)
                thread.start()
            elif isinstance(worker, ProcessWorker):
                thread.quit()
                worker._start()
            self._threads.append(thread)
        else:
            self._timer.start()

        if self._workers:
            for w in self._workers:
                if w.is_finished():
                    self._bag_collector.append(w)
                    self._workers.remove(w)

        if self._threads:
            for t in self._threads:
                if t.isFinished():
                    self._threads.remove(t)
                    self._running_threads -= 1

        if len(self._threads) == 0 and len(self._workers) == 0:
            self._timer.stop()
            self._timer_worker_delete.start()
Beispiel #14
0
class PMGQThreadManager(QObject):
    signal_server_message_received = Signal(dict)
    signal_data_changed = Signal(str)
    signal_finished = Signal()

    def __init__(self, parent=None, worker: QObject = None):
        super().__init__(parent)
        self.thread_recv = QThread()
        self.worker_recv = worker
        self.worker_recv.moveToThread(self.thread_recv)
        self.thread_recv.started.connect(self.worker_recv.work)
        self.thread_recv.start()
        self.thread_recv.finished.connect(self.signal_finished.emit)

    def shut_down(self):
        """
        关闭线程,并且退出。
        :return:
        """
        self.worker_recv.on_exit()
        self.thread_recv.quit()
        self.thread_recv.wait(500)
Beispiel #15
0
class TimerThread(QObject):
    signal = Signal(str)

    def __init__(self, callback, milis):
        super().__init__()
        self.milis = milis
        self.signal.connect(callback)
        self.thread = QThread()
        self.timer = QTimer()
        #self.timer.timeout.connect(self.timeout)
        #self.timer.start(milis)
        self.thread.started.connect(self.init)

    def Start(self):
        self.thread.start()

    @Slot()
    def init(self):
        self.timer.timeout.connect(self.timeout)
        self.timer.start(self.milis)

    def timeout(self):
        self.signal.emit("tick")
Beispiel #16
0
class Runner:
    """
    init -> register -> start
    init -> start -> register
    """
    def __init__(self, task_cls: Type[Task], *args, **kwargs):
        super().__init__()

        self.task: Optional[Task] = None
        self.thread: Optional[QThread] = None

        self._started = False

        self.task_cls = task_cls
        self._args = args
        self._kwargs = kwargs

    def init(self) -> None:
        if self._started:
            raise RuntimeError("Cannot init runner when task already started!")

        self.thread = QThread()

        self.task = self.task_cls(*self._args, **self._kwargs)
        self.task.moveToThread(self.thread)
        self.task.finished.connect(self.task.deleteLater)
        self.task.finished.connect(self.thread.quit)
        self.task.failed.connect(self.fail)
        self.thread.started.connect(
            self.task.execute)  # lambdas don't work here hm
        self.thread.finished.connect(self.thread.deleteLater)

    def register(self,
                 *,
                 started: Callable = None,
                 finished: Callable = None,
                 progress: Callable = None,
                 failed: Callable = None,
                 result: Callable = None) -> None:
        if started is not None:
            self.task.started.connect(started)
        if finished is not None:
            self.task.finished.connect(finished)
        if progress is not None:
            self.task._progressThrottled.connect(progress)
        if failed is not None:
            self.task.failed.connect(failed)
        if result is not None:
            self.task.result.connect(result)

    def start(self) -> None:
        self._started = True
        self.thread.start()

    def stop(self) -> None:
        if self.task is not None:
            self.task.stop()

        if self.thread is not None:
            try:
                self.thread.quit()
                self.thread.wait()
            except RuntimeError:
                pass

        self._started = False

    def fail(self, e: Exception) -> None:
        raise e

    @property
    def is_running(self) -> bool:
        """
        Without this try-except block, Qt throws an error saying that the thread has
        already been deleted, even if `self.thread` is not None.
        """
        is_running = False
        try:
            if self.thread is not None:
                is_running = self.thread.isRunning()
        except RuntimeError:
            pass

        return is_running
Beispiel #17
0
class KiteClient(QObject, KiteMethodProviderMixIn):
    sig_response_ready = Signal(int, dict)
    sig_client_started = Signal(list)
    sig_client_not_responding = Signal()
    sig_perform_request = Signal(int, str, object)
    sig_perform_status_request = Signal(str)
    sig_status_response_ready = Signal((str, ), (dict, ))
    sig_perform_onboarding_request = Signal()
    sig_onboarding_response_ready = Signal(str)

    def __init__(self, parent, enable_code_snippets=True):
        QObject.__init__(self, parent)
        self.endpoint = None
        self.requests = {}
        self.languages = []
        self.mutex = QMutex()
        self.opened_files = {}
        self.opened_files_status = {}
        self.thread_started = False
        self.enable_code_snippets = enable_code_snippets
        self.thread = QThread()
        self.moveToThread(self.thread)
        self.thread.started.connect(self.started)
        self.sig_perform_request.connect(self.perform_request)
        self.sig_perform_status_request.connect(self.get_status)
        self.sig_perform_onboarding_request.connect(self.get_onboarding_file)

    def start(self):
        if not self.thread_started:
            self.thread.start()
        logger.debug('Starting Kite HTTP session...')
        self.endpoint = requests.Session()
        self.languages = self.get_languages()
        self.sig_client_started.emit(self.languages)

    def started(self):
        self.thread_started = True

    def stop(self):
        if self.thread_started:
            logger.debug('Closing Kite HTTP session...')
            self.endpoint.close()
            self.thread.quit()

    def get_languages(self):
        verb, url = KITE_ENDPOINTS.LANGUAGES_ENDPOINT
        success, response = self.perform_http_request(verb, url)
        if response is None:
            response = ['python']
        return response

    def _get_onboarding_file(self):
        """Perform a request to get kite's onboarding file."""
        verb, url = KITE_ENDPOINTS.ONBOARDING_ENDPOINT
        success, response = self.perform_http_request(verb, url)
        return response

    def get_onboarding_file(self):
        """Get onboarding file."""
        onboarding_file = self._get_onboarding_file()
        self.sig_onboarding_response_ready.emit(onboarding_file)

    def _get_status(self, filename):
        """Perform a request to get kite status for a file."""
        verb, url = KITE_ENDPOINTS.STATUS_ENDPOINT
        if filename:
            url_params = {'filename': filename}
        else:
            url_params = {'filetype': 'python'}
        success, response = self.perform_http_request(verb,
                                                      url,
                                                      url_params=url_params)
        return response

    def get_status(self, filename):
        """Get kite status for a given filename."""
        kite_status = self._get_status(filename)
        if not filename or kite_status is None:
            kite_status = status()
            self.sig_status_response_ready[str].emit(kite_status)
        else:
            self.sig_status_response_ready[dict].emit(kite_status)

    def perform_http_request(self, verb, url, url_params=None, params=None):
        response = None
        http_method = getattr(self.endpoint, verb)
        try:
            http_response = http_method(url, params=url_params, json=params)
        except Exception as error:
            return False, None
        success = http_response.status_code == 200
        if success:
            try:
                response = http_response.json()
            except Exception:
                response = http_response.text
                response = None if response == '' else response
        return success, response

    def send(self, method, params, url_params):
        response = None
        if self.endpoint is not None and method in KITE_REQUEST_MAPPING:
            http_verb, path = KITE_REQUEST_MAPPING[method]
            encoded_url_params = {
                key: quote(value) if isinstance(value, TEXT_TYPES) else value
                for (key, value) in url_params.items()
            }
            path = path.format(**encoded_url_params)
            try:
                success, response = self.perform_http_request(http_verb,
                                                              path,
                                                              params=params)
            except (ConnectionRefusedError, ConnectionError):
                return response
        return response

    def perform_request(self, req_id, method, params):
        response = None
        if method in self.sender_registry:
            logger.debug('Perform {0} request with id {1}'.format(
                method, req_id))
            handler_name = self.sender_registry[method]
            handler = getattr(self, handler_name)
            response = handler(params)
            if method in self.handler_registry:
                converter_name = self.handler_registry[method]
                converter = getattr(self, converter_name)
                if response is not None:
                    response = converter(response)
        self.sig_response_ready.emit(req_id, response or {})
Beispiel #18
0
class QBaseServer(QObject):
    """Server base for QtPyNetwork."""
    connected = Signal(Device, str, int)
    disconnected = Signal(Device)
    message = Signal(Device, bytes)
    error = Signal(Device, str)

    server_error = Signal(str)
    closed = Signal()

    def __init__(self, loggerName=None):
        super(QBaseServer, self).__init__()
        if loggerName:
            self.__logger = logging.getLogger(loggerName)
        else:
            self.__logger = logging.getLogger(self.__class__.__name__)
        self.__ip = None
        self.__port = None

        self.__devices = []
        self.__deviceModel = Device

        self.__handler = None
        self.__handler_thread = None
        self.__handlerClass = None
        self.__server = None

    @Slot(str, int, bytes)
    def start(self, ip: str, port: int):
        """Start server."""
        if self.__handlerClass:
            ip = QHostAddress(ip)
            self.__ip = ip
            self.__port = port
            self.__handler = self.__handlerClass()
            self.__handler_thread = QThread()
            self.__handler.moveToThread(self.__handler_thread)

            self.__handler.connected.connect(
                self.__on_handler_successful_connection)
            self.__handler.message.connect(self.__on_handler_device_message)
            self.__handler.error.connect(self.__on_handler_device_error)
            self.__handler.disconnected.connect(
                self.__on_handler_device_disconnected)
            self.__handler.closed.connect(self.on_closed)

            self.__handler_thread.started.connect(self.__handler.start)
            self.__handler.started.connect(self.__setup_server)
            self.__handler_thread.start()
        else:
            raise Exception("Handler class not set!")

    @Slot()
    def __setup_server(self):
        """Create QTCPServer, start listening for connections."""
        self.__server = TCPServer()
        self.__server.connection.connect(self.__handler.on_incoming_connection)
        if self.__server.listen(self.__ip, self.__port):
            self.__logger.info("Started listening for connections")
        else:
            e = self.__server.errorString()
            self.__logger.error(e)
            self.server_error.emit(e)

    @Slot(int, str, int)
    def __on_handler_successful_connection(self, device_id, ip, port):
        """When client connects to server successfully."""
        device = self.__deviceModel(self, device_id, ip, port)
        self.__devices.append(device)
        self.__logger.info("Added new CLIENT-{} with address {}:{}".format(
            device_id, ip, port))
        self.on_connected(device, ip, port)

    @Slot(int, bytes)
    def __on_handler_device_message(self, device_id: int, message: bytes):
        """When server receives message from bot."""
        self.on_message(self.get_device_by_id(device_id), message)

    @Slot(int)
    def __on_handler_device_disconnected(self, device_id):
        """When bot disconnects from server."""
        device = self.get_device_by_id(device_id)
        device.set_connected(False)
        if device in self.__devices:
            self.__devices.remove(device)
        self.on_disconnected(device)

    @Slot(int, str)
    def __on_handler_device_error(self, device_id, error):
        self.on_error(self.get_device_by_id(device_id), error)

    @Slot(Device, str, int)
    def on_connected(self, device: Device, ip: str, port: int):
        """Called when new client connects to server.
        Emits connected signal.

        Args:
            device (Device): Device object.
            ip (str): Client ip address.
            port (int): Client port.
        """
        self.connected.emit(device, ip, port)

    @Slot(Device, bytes)
    def on_message(self, device: Device, message: bytes):
        """Called when server receives message from client.
        Emits message signal.

        Args:
            device (Device): Message sender.
            message (bytes): Message.
        """
        self.message.emit(device, message)

    @Slot(Device)
    def on_disconnected(self, device: Device):
        """Called when device disconnects from server.
        Emits disconnected signal.

        Args:
            device (Device): Disconnected device.
        """
        self.disconnected.emit(device)

    @Slot(Device, str)
    def on_error(self, device: Device, error: str):
        """Called when a socket error occurs.
        Emits error signal.

        Args:
            device (Device): Device object.
            error (str): Error string.
        """
        self.error.emit(device, error)

    @Slot()
    def on_closed(self):
        self.closed.emit()

    @Slot(Device, bytes)
    def write(self, device: Device, data: bytes):
        """Write data to device."""
        if not self.__server or not self.__handler:
            raise ServerNotRunning("Server is not running")
        if not device.is_connected():
            raise NotConnectedError("Client is not connected")
        self.__handler.write.emit(device.id(), data)

    @Slot(bytes)
    def write_all(self, data: bytes):
        """Write data to all devices."""
        if not self.__server or not self.__handler:
            raise ServerNotRunning("Server is not running")
        self.__handler.write_all.emit(data)

    @Slot()
    def kick(self, device: Device):
        """Disconnect device from server."""
        if not self.__server or not self.__handler:
            raise ServerNotRunning("Server is not running")
        if not device.is_connected():
            raise NotConnectedError("Client is not connected")
        self.__handler.kick.emit(device.id())

    @Slot()
    def close(self):
        """Disconnect clients and close server."""
        self.__logger.info("Closing server...")
        if self.__server:
            self.__server.close()
        if self.__handler:
            self.__handler.close()
            self.__handler_thread.quit()

    def set_device_model(self, model):
        """Set model to use for device when client connects.

        Note:
            Model should be subclassing Device.
        """
        if self.is_running():
            raise Exception("Set device model before starting server!")

        if not issubclass(model, Device):
            raise ValueError("Model should be subclassing Device class.")

        try:
            model(QBaseServer(), 0, "127.0.0.1", 5000)
        except TypeError as e:
            raise TypeError(
                "Model is not valid class! Exception: {}".format(e))

        self.__deviceModel = model

    def is_running(self):
        """Check if server is running."""
        if self.__handler_thread:
            return self.__handler_thread.isRunning()
        return False

    def wait(self):
        """Wait for server thread to close."""
        if self.__handler_thread:
            return self.__handler_thread.wait()
        return True

    @Slot(int)
    def get_device_by_id(self, device_id: int) -> Device:
        """Returns device with associated ID.

        Args:
            device_id (int): Device ID.
        """
        for device in self.__devices:
            if device.id() == device_id:
                return device
        raise Exception("CLIENT-{} not found".format(device_id))

    def get_devices(self):
        """Returns list with devices."""
        return self.__devices

    def set_handler_class(self, handler):
        """Set handler to use. This should not be used
        outside this library."""
        if self.is_running():
            raise Exception("Set socket handler before starting server!")
        try:
            handler()
        except TypeError as e:
            raise TypeError(
                "Handler is not valid class! Exception: {}".format(e))
        self.__handlerClass = handler
Beispiel #19
0
class KiteClient(QObject, KiteMethodProviderMixIn):
    sig_response_ready = Signal(int, dict)
    sig_client_started = Signal(list)
    sig_client_not_responding = Signal()
    sig_perform_request = Signal(int, str, object)

    MAX_SERVER_CONTACT_RETRIES = 40

    def __init__(self, parent):
        QObject.__init__(self, parent)
        self.contact_retries = 0
        self.endpoint = None
        self.requests = {}
        self.languages = []
        self.mutex = QMutex()
        self.opened_files = {}
        self.alive = False
        self.thread_started = False
        self.thread = QThread()
        self.moveToThread(self.thread)
        self.thread.started.connect(self.started)
        self.sig_perform_request.connect(self.perform_request)

    def __wait_http_session_to_start(self):
        logger.debug('Waiting Kite HTTP endpoint to be available...')
        _, url = KITE_ENDPOINTS.ALIVE_ENDPOINT
        try:
            code = requests.get(url).status_code
        except Exception:
            code = 500

        if self.contact_retries == self.MAX_SERVER_CONTACT_RETRIES:
            logger.debug('Kite server is not answering')
            self.sig_client_not_responding.emit()
        elif code != 200:
            self.contact_retries += 1
            QTimer.singleShot(250, self.__wait_http_session_to_start)
        elif code == 200:
            self.alive = True
            self.start_client()

    def start(self):
        if not self.thread_started:
            self.thread.start()
        self.__wait_http_session_to_start()

    def start_client(self):
        logger.debug('Starting Kite HTTP session...')
        self.endpoint = requests.Session()
        self.languages = self.get_languages()
        self.sig_client_started.emit(self.languages)

    def started(self):
        self.thread_started = True

    def stop(self):
        if self.thread_started:
            logger.debug('Closing Kite HTTP session...')
            self.endpoint.close()
            self.thread.quit()

    def get_languages(self):
        verb, url = KITE_ENDPOINTS.LANGUAGES_ENDPOINT
        success, response = self.perform_http_request(verb, url)
        return response

    def perform_http_request(self, verb, url, params=None):
        response = None
        success = False
        http_method = getattr(self.endpoint, verb)
        http_response = http_method(url, json=params)
        success = http_response.status_code == 200
        if success:
            try:
                response = http_response.json()
            except Exception:
                response = http_response.text
                response = None if response == '' else response
        return success, response

    def send(self, method, params, url_params):
        response = None
        if self.endpoint is not None and method in KITE_REQUEST_MAPPING:
            if self.alive:
                http_verb, path = KITE_REQUEST_MAPPING[method]
                path = path.format(**url_params)
                try:
                    success, response = self.perform_http_request(
                        http_verb, path, params)
                except (ConnectionRefusedError, ConnectionError):
                    self.alive = False
                    self.endpoint = None
                    self.contact_retries = 0
                    self.__wait_http_session_to_start()
                    return response
        return response

    def perform_request(self, req_id, method, params):
        if method in self.sender_registry:
            logger.debug('Perform {0} request with id {1}'.format(
                method, req_id))
            handler_name = self.sender_registry[method]
            handler = getattr(self, handler_name)
            response = handler(params)
            if method in self.handler_registry:
                converter_name = self.handler_registry[method]
                converter = getattr(self, converter_name)
                response = converter(response)
            if response is not None:
                self.sig_response_ready.emit(req_id, response)
Beispiel #20
0
class FileDialog(QDialog):
    def __init__(self,
                 file_name,
                 job_name,
                 job_number,
                 realization,
                 iteration,
                 parent=None):
        super(FileDialog, self).__init__(parent)

        self.setWindowTitle("{} # {} Realization: {} Iteration: {}" \
                            .format(job_name, job_number, realization, iteration))

        self._file_name = file_name
        try:
            self._file = open(file_name, "r")
        except OSError as error:
            self._mb = QMessageBox(QMessageBox.Critical, "Error opening file",
                                   error.strerror, QMessageBox.Ok, self)
            self._mb.finished.connect(self.accept)
            self._mb.show()
            return

        self._model = FileModel()
        self._view = FileView()
        self._view.setModel(self._model)

        self._init_layout()
        self._init_thread()

        self.show()

    @Slot()
    def _stop_thread(self):
        self._thread.quit()
        self._thread.wait()

    def _init_layout(self):
        self.setMinimumWidth(400)
        self.setMinimumHeight(200)

        dialog_buttons = QDialogButtonBox(QDialogButtonBox.Ok)
        self._follow = dialog_buttons.addButton("Follow",
                                                QDialogButtonBox.ActionRole)
        self._copy_all = dialog_buttons.addButton("Copy All",
                                                  QDialogButtonBox.ActionRole)
        dialog_buttons.accepted.connect(self.accept)

        self._follow.setCheckable(True)
        self._follow.toggled.connect(self._view.enable_follow_mode)
        self._copy_all.clicked.connect(self._model.copy_all)

        layout = QVBoxLayout(self)
        layout.addWidget(self._view)
        layout.addWidget(dialog_buttons)

    def _init_thread(self):
        self._thread = QThread()

        self._worker = FileUpdateWorker(self._file)
        self._worker.moveToThread(self._thread)
        self._worker.read.connect(self._model.append_text)

        self._thread.started.connect(self._worker.setup)
        self._thread.finished.connect(self._worker.stop)
        self._thread.finished.connect(self._worker.deleteLater)
        self.finished.connect(self._stop_thread)
        self._thread.start()
Beispiel #21
0
class SpectralOperationHandler(QDialog):
    """
    Widget to handle user interactions with operations that are communicated
    from the SpecViz viewer. This is built to work with
    :func:`~spectral_cube.SpectralCube.apply_function` method by passing in a
    callable :class:`specviz.analysis.operations.FunctionalOperation` object.

    Parameters
    ----------
    data : :class:`~glue.core.data.Data`
        Glue data object on which the spectral operation will be performed.
    function : :class:`specviz.analysis.operations.FunctionalOperation`
        Python class instance whose `call` function will be performed on the
        :class:`~spectral_cube.SpectralCube` object.
    """
    def __init__(self, data, component_id, layout, function=None, func_proxy=None,
                 stack=None, operation_name=None, ui_settings=None, *args, **kwargs):
        super(SpectralOperationHandler, self).__init__(*args, **kwargs)
        self._data = data
        self._stack = stack
        self._func_proxy = func_proxy
        self._function = function or (stack[0] if stack is not None else None)
        self._operation_name = operation_name or (self._function.function.__name__
            if self._function is not None else "None")
        self._component_id = component_id
        self._operation_thread = None
        self._layout = layout
        self._ui_settings = ui_settings

        self._op_thread = None

        self.setup_ui()
        self.setup_connections()

    def setup_ui(self):
        """Setup the PyQt UI for this dialog."""
        # Load the ui dialog
        loadUi(os.path.join(os.path.dirname(__file__), "operation_dialog.ui"), self)

        if self._ui_settings is not None:
            self.setWindowTitle(self._ui_settings.get("title"))
            self.operation_group_box.setTitle(self._ui_settings.get("group_box_title"))
            self.description_label.setText(self._ui_settings.get("description"))

        component_ids = [str(i) for i in self._data.component_ids()]
        cur_ind = self._data.component_ids().index(self._component_id)

        if self._stack is not None:
            operation_stack = []

            for oper in self._stack:
                func_params = []

                for arg in oper.args:
                    func_params.append("{}".format(arg))

                for k, v in oper.kwargs.items():
                    func_params.append("{}={}".format(k,
                                                      str(v)[:10] + "... " + str(
                                                          v)[-5:] if len(
                                                          str(v)) > 15 else str(
                                                          v)))

                operation_stack.append("{}({})".format(oper.function.__name__,
                                                       ", ".join(func_params)))

            self.operation_combo_box.addItems(operation_stack)
        else:
            self.operation_combo_box.addItem(self._operation_name)

        # Populate combo box
        self.data_component_combo_box.addItems(component_ids)
        self.data_component_combo_box.setCurrentIndex(cur_ind)

        # Disable the button box if there are no available operations
        if self._function is None:
            self.button_box.button(QDialogButtonBox.Ok).setEnabled(False)

    def setup_connections(self):
        """Setup signal/slot connections for this dialog."""
        # When an operation is selected, update the function reference
        self.operation_combo_box.currentIndexChanged.connect(
            self.on_operation_index_changed)

        # When a data component is selected, update the data object reference
        self.data_component_combo_box.currentIndexChanged.connect(
            self.on_data_component_index_changed)

        # If the abort button is clicked, attempted to stop execution
        self.abort_button.clicked.connect(self.on_aborted)

    def _compose_cube(self):
        """
        Create a :class:`~spectral_cube.SpectralCube` from a Glue data
        component.
        """
        if issubclass(self._data.__class__, Subset):
            wcs = self._data.data.coords.wcs
            data = self._data.data
            mask = self._data.to_mask()
        else:
            wcs = self._data.coords.wcs
            data = self._data
            mask = np.ones(self._data.shape).astype(bool)

        mask = BooleanArrayMask(mask=mask, wcs=wcs)

        return SpectralCube(data[self._component_id], wcs=wcs, mask=mask,
                            meta={'unit':self._data.get_component(self._component_id).units})

    def on_operation_index_changed(self, index):
        """Called when the index of the operation combo box has changed."""
        self._function = self._stack[index]

    def on_data_component_index_changed(self, index):
        """Called when the index of the component combo box has changed."""
        self._component_id = self._data.component_ids()[index]

    def accept(self):
        """Called when the user clicks the "Okay" button of the dialog."""
        # Show the progress bar and abort button
        self.progress_bar.setEnabled(True)
        self.abort_button.setEnabled(True)

        self.button_box.button(QDialogButtonBox.Ok).setEnabled(False)
        self.button_box.button(QDialogButtonBox.Cancel).setEnabled(False)

        if self._func_proxy is not None:
            op_func = lambda *args, **kwargs: self._func_proxy(self._function,
                                                               *args, **kwargs)
        else:
            op_func = self._function

        self._op_thread = QThread()

        self._op_worker = OperationWorker(self._compose_cube(), op_func)
        self._op_worker.moveToThread(self._op_thread)
        self._op_worker.result.connect(self.on_finished)
        self._op_worker.status.connect(self.on_status_updated)

        self._op_thread.started.connect(self._op_worker.run)
        self._op_thread.start()

        # data, unit = op_func(self._compose_cube(), None)
        # self.on_finished(data, unit)

    def on_aborted(self):
        """Called when the user aborts the operation."""
        self._op_thread.terminate()
        self.progress_bar.reset()

        # Hide the progress bar and abort button
        self.abort_button.setEnabled(False)

        self.button_box.button(QDialogButtonBox.Ok).setEnabled(True)
        self.button_box.button(QDialogButtonBox.Cancel).setEnabled(True)

    def on_status_updated(self, value):
        """
        Called when the status of the operation has been updated. This can be
        optionally be passed a value to use as the new progress bar value.

        Parameters
        ----------
        value : float
            The value passed to the :class:`~qtpy.QtWidgets.QProgressBar`
            instance.
        """
        self.progress_bar.setValue(value * 100)

    def on_finished(self, data, unit=None):
        """
        Called when the `QThread` has finished performing the operation on the
        `SpectralCube` object.

        Parameters
        ----------
        data : ndarray
            The result of the operation performed on the `SpectralCube` object.
        """
        component_name = "{} {}".format(self._component_id,
                                        self._operation_name)

        comp_count = len([x for x in self._data.component_ids()
                          if component_name in str(x)])

        if comp_count > 0:
            component_name = "{} {}".format(component_name, comp_count)

        if data.ndim == 2:
            coords = WCSCoordinates(wcs=self._data.coords.wcs.celestial)

            self._data.container_2d = Data(label=self._data.label + " [2d]",
                                           coords=coords)
            self._data.container_2d.add_component(data, component_name)

            # self._layout.session.data_collection.append(self._data.container_2d)
            self._layout.add_overlay(data, component_name, display_now=True)
        else:
            component = Component(data, units=unit)
            self._data.add_component(component, component_name)

        self._op_thread.exit()
        super(SpectralOperationHandler, self).accept()
Beispiel #22
0
class PMGIpythonConsole(RichJupyterWidget):
    def __init__(self, *args, **kwargs):
        super(PMGIpythonConsole, self).__init__(*args, **kwargs)
        self.is_first_execution = True
        self.confirm_restart = False

        self.commands_pool = []
        self.command_callback_pool: Dict[str, Callable] = {}

    def change_ui_theme(self, style: str):
        """
        改变界面主题颜色
        :param style:
        :return:
        """
        style = style.lower()
        if style == 'fusion':
            self.style_sheet = default_light_style_sheet
            self.syntax_style = default_light_syntax_style

        elif style == 'qdarkstyle':
            self.style_sheet = default_dark_style_sheet
            self.syntax_style = default_dark_syntax_style

        elif style.lower() == 'windowsvista':
            self.style_sheet = default_light_style_sheet
            self.syntax_style = default_light_syntax_style

        elif style.lower() == 'windows':
            self.style_sheet = default_light_style_sheet
            self.syntax_style = default_light_syntax_style

    def _handle_kernel_died(self, since_last_heartbit):
        self.is_first_execution = True
        self.restart_kernel(None, True)
        self.initialize_ipython_builtins()
        self.execute_command('')
        return True

    def _handle_execute_input(self, msg):
        super()._handle_execute_result(msg)

    def setup_ui(self):
        self.kernel_manager = None
        self.kernel_client = None
        # initialize by thread
        self.init_thread = QThread(self)
        self.console_object = ConsoleInitThread()
        self.console_object.moveToThread(self.init_thread)
        self.console_object.initialized.connect(self.slot_initialized)
        self.init_thread.finished.connect(self.console_object.deleteLater)
        self.init_thread.finished.connect(self.init_thread.deleteLater)
        self.init_thread.started.connect(self.console_object.run)
        self.init_thread.start()
        cursor: QTextCursor = self._prompt_cursor
        cursor.movePosition(QTextCursor.End)

    def _context_menu_make(self, pos: 'QPoint') -> QMenu:
        menu = super(PMGIpythonConsole, self)._context_menu_make(pos)
        _translate = QCoreApplication.translate
        trans_dic = {
            'Cut': _translate("PMGIpythonConsole", 'Cut'),
            'Copy': _translate("PMGIpythonConsole", 'Copy'),
            'Copy (Raw Text)': _translate("PMGIpythonConsole",
                                          'Copy(Raw Text)'),
            'Paste': _translate("PMGIpythonConsole", 'Paste'),
            'Select All': _translate("PMGIpythonConsole", 'Select All'),
            'Save as HTML/XML': _translate("PMGIpythonConsole",
                                           'Save as HTML/XML'),
            'Print': _translate("PMGIpythonConsole", 'Print')
        }
        for action in menu.actions():
            trans = trans_dic.get(action.text())
            trans = trans if trans is not None else action.text()
            action.setText(trans)
        restart_action = menu.addAction(
            _translate("PMGIpythonConsole", 'Restart'))
        restart_action.triggered.connect(self.slot_restart_kernel)

        stop_action = menu.addAction(
            _translate("PMGIpythonConsole", 'Interrupt'))
        # stop_action.triggered.connect(self.request_interrupt_kernel)
        stop_action.triggered.connect(self.on_interrupt_kernel)
        # stop_action.setEnabled(self._executing)

        return menu

    def on_interrupt_kernel(self):
        """
        当点击中断执行时。
        IPython会出现一个奇怪的问题——当中断执行时,可能_executing恒为False。
        因此干脆不屏蔽了。
        Returns:

        """
        self.interrupt_kernel()

    def _custom_context_menu_requested(self, pos):
        super(PMGIpythonConsole, self)._custom_context_menu_requested(pos)

    def slot_restart_kernel(self, arg):
        ret = QMessageBox.warning(self, '提示', '是否要重启控制台?\n一切变量都将被重置。',
                                  QMessageBox.Ok | QMessageBox.Cancel,
                                  QMessageBox.Cancel)
        if ret == QMessageBox.Ok:
            self._restart_kernel(arg)

    def _restart_kernel(self, arg1):

        self.is_first_execution = True
        self.restart_kernel(None, True)
        self.initialize_ipython_builtins()
        self.execute_command('')
        return True

    def slot_initialized(self, kernel_manager, kernel_client):
        """
        Args:
            kernel_manager: `qtconsole.manager.QtKernelManager`
            kernel_client: `qtconsole.manager.QtKernelManager.client`

        Returns:
        """
        self.kernel_manager = kernel_manager
        self.kernel_client = kernel_client
        self.initialize_ipython_builtins()

    def initialize_ipython_builtins(self):
        return

    def _update_list(self):
        try:
            super(PMGIpythonConsole, self)._update_list()
        except BaseException:
            import traceback
            traceback.print_exc()

    def _banner_default(self):
        """
        自定义控制台开始的文字
        Returns:
        """
        return 'Welcome To PMGWidgets Ipython Console!\n'

    def closeEvent(self, event):
        if self.init_thread.isRunning():
            self.console_object.stop()
            self.init_thread.quit()
            self.init_thread.wait(500)
        super(PMGIpythonConsole, self).closeEvent(event)

    def execute_file(self, file: str, hidden: bool = False):
        if not os.path.exists(file) or not file.endswith('.py'):
            raise FileNotFoundError(f'{file} not found or invalid')
        base = os.path.basename(file)
        cmd = os.path.splitext(base)[0]
        with open(file, 'r', encoding='utf-8') as f:
            source = f.read()

        self.execute_command(source, hidden=hidden, hint_text=cmd)

    def execute_command(self,
                        source,
                        hidden: bool = False,
                        hint_text: str = '') -> str:
        """

        :param source:
        :param hidden:
        :param hint_text: 运行代码前显示的提示
        :return: str 执行命令的 msgid
        """
        cursor: QTextCursor = self._prompt_cursor
        cursor.movePosition(QTextCursor.End)
        # 运行文件时,显示文件名,无换行符,执行选中内容时,包含换行符
        # 检测换行符,在ipy console中显示执行脚本内容
        hint_row_list = hint_text.split("\n")
        for hint in hint_row_list:
            if hint != "":
                cursor.insertText('%s\n' % hint)
                self._insert_continuation_prompt(cursor)
        else:
            # 删除多余的continuation_prompt
            self.undo()

        self._finalize_input_request(
        )  # display input string buffer in console.
        cursor.movePosition(QTextCursor.End)
        if self.kernel_client is None:
            self.commands_pool.append((source, hidden, hint_text))
            return ''
        else:
            return self.pmexecute(source, hidden)

    def _handle_stream(self, msg):
        parent_header = msg.get('parent_header')
        if parent_header is not None:
            msg_id = parent_header.get(
                'msg_id')  # 'fee0bee5-074c00d093b1455be6d166b1_10'']
            if msg_id in self.command_callback_pool.keys():
                callback = self.command_callback_pool.pop(msg_id)
                assert callable(callback)
                callback()
        cursor: QTextCursor = self._prompt_cursor
        cursor.movePosition(QTextCursor.End)
        super()._handle_stream(msg)

    def append_stream(self, text):
        """重写的方法。原本before_prompt属性是False。"""
        self._append_plain_text(text, before_prompt=False)

    def pmexecute(self, source: str, hidden: bool = False) -> str:
        """
        执行代码并且返回Msgid
        :param source:
        :param hidden:
        :return:
        """
        is_legal, msg = self.is_source_code_legal(source)
        if not is_legal:
            QMessageBox.warning(self, '警告', msg)
            source = ''
        msg_id = self.kernel_client.execute(source, hidden)
        self._request_info['execute'][msg_id] = self._ExecutionRequest(
            msg_id, 'user')
        self._hidden = hidden
        if not hidden:
            self.executing.emit(source)
        return msg_id
        # super()._execute(source, hidden)

    def is_source_code_legal(self, source_code: str) -> Tuple[bool, str]:
        """判断注入到shell中的命令是否合法。

        如果命令不合法,应当避免执行该命令。

        Args:
            source_code: 注入到shell中的命令。

        Returns:
            * 是否合法;
            * 如不合法,返回原因;如合法,返回空字符串。

        """
        return True, ''

    @staticmethod
    def install_translator():
        global _trans
        app = QApplication.instance()
        assert app is not None
        _trans = QTranslator()
        _trans.load(QLocale.system(),
                    'qt_zh_CN.qm',
                    directory=os.path.join(os.path.dirname(__file__),
                                           'translations'))
        app.installTranslator(_trans)
Beispiel #23
0
class GistWindow(QDialog):
    def __init__(self):
        super(GistWindow, self).__init__()

        self.thread = None
        self.worker = None
        self.gistToken = config.gistToken
        self.connected = False

        self.setWindowTitle("Gist")
        self.setMinimumWidth(380)
        self.layout = QVBoxLayout()

        self.testStatus = QLabel("")
        self.layout.addWidget(self.testStatus)

        self.layout.addWidget(QLabel("Gist Token"))
        self.gistTokenInput = QLineEdit()
        self.gistTokenInput.setText(self.gistToken)
        self.gistTokenInput.setMaxLength(40)
        self.gistTokenInput.textChanged.connect(self.enableButtons)
        self.layout.addWidget(self.gistTokenInput)

        self.testButton = QPushButton("Test Connection")
        self.testButton.setEnabled(False)
        self.testButton.clicked.connect(self.checkStatus)
        self.layout.addWidget(self.testButton)

        # self.syncHighlightsButton = QPushButton("Synch Highlights")
        # self.syncHighlightsButton.setEnabled(False)
        # self.syncHighlightsButton.clicked.connect(self.syncHighlights)
        # self.layout.addWidget(self.syncHighlightsButton)

        self.syncBibleNotesButton = QPushButton("Synch Bibles Notes")
        self.syncBibleNotesButton.setEnabled(False)
        self.syncBibleNotesButton.clicked.connect(self.syncBibleNotes)
        self.layout.addWidget(self.syncBibleNotesButton)

        buttons = QDialogButtonBox.Ok
        self.buttonBox = QDialogButtonBox(buttons)
        self.buttonBox.accepted.connect(self.accept)
        self.buttonBox.accepted.connect(self.stopSync)
        self.layout.addWidget(self.buttonBox)
        self.setLayout(self.layout)

        self.enableButtons()
        self.checkStatus()

    def enableButtons(self):
        if len(self.gistTokenInput.text()) >= 40:
            self.testButton.setEnabled(True)
        else:
            self.testButton.setEnabled(False)
            self.connected = False
            self.setStatus("Not connected", False)
        if self.connected:
            self.testButton.setEnabled(False)
            self.syncBibleNotesButton.setEnabled(True)
        else:
            self.syncBibleNotesButton.setEnabled(False)

    def checkStatus(self):
        if len(self.gistTokenInput.text()) < 40:
            self.setStatus("Not connected", False)
            self.connected = False
        else:
            self.gh = GitHubGist(self.gistTokenInput.text())
            if self.gh.connected:
                self.setStatus("Connected to " + self.gh.user.name, True)
                self.connected = True
                config.gistToken = self.gistTokenInput.text()
            else:
                self.setStatus("Not connected", False)
                self.connected = False
        self.enableButtons()

    def setStatus(self, message, connected=True):
        self.testStatus.setText("Status: " + message)
        if connected:
            self.testStatus.setStyleSheet("color: rgb(128, 255, 7);")
        else:
            self.testStatus.setStyleSheet("color: rgb(253, 128, 8);")
        QApplication.processEvents()

    def syncBibleNotes(self):
        self.setStatus("Syncing ...", True)
        self.syncBibleNotesButton.setEnabled(False)

        self.thread = QThread()
        self.worker = SyncNotesWithGist()
        self.worker.moveToThread(self.thread)
        self.thread.started.connect(self.worker.run)
        self.worker.finished.connect(self.thread.quit)
        self.worker.finished.connect(self.worker.deleteLater)
        self.thread.finished.connect(self.worker.deleteLater)
        self.worker.finished.connect(self.syncCompleted)
        self.worker.progress.connect(self.setStatus)
        self.thread.start()

    def syncCompleted(self, count):
        self.setStatus("Done! Processed {0} notes".format(count), True)

    def stopSync(self):
        if self.thread and self.thread.isRunning():
            self.thread.quit()
Beispiel #24
0
def new_worker_qthread(
    Worker: Type[QObject], *args, start=False, connections=None, **kwargs
):
    """This is a convenience method to start a worker in a Qthread

    It follows the pattern described here:
    https://www.qt.io/blog/2010/06/17/youre-doing-it-wrong
    and
    https://doc.qt.io/qt-5/qthread.html#details

    all *args, **kwargs will be passed to the Worker class on instantiation.

    Parameters
    ----------
    Worker : QObject
        QObject type that implements a work() method.  The Worker should also
        emit a finished signal when the work is done.
    start : bool
        If True, worker will be started immediately, otherwise, you must
        manually start the worker.
    connections: dict, optional
        Optional dictionary of {signal: function} to connect to the new worker.
        for instance:  connections = {'incremented': myfunc} will result in:
        worker.incremented.connect(myfunc)

    Examples
    --------
    Create some QObject that has a long-running work method:

    >>> class Worker(QObject):
    ...
    ...     finished = Signal()
    ...     increment = Signal(int)
    ...
    ...     def __init__(self, argument):
    ...         super().__init__()
    ...         self.argument = argument
    ...
    ...     @Slot()
    ...     def work(self):
    ...         # some long running task...
    ...         import time
    ...         for i in range(10):
    ...             time.sleep(1)
    ...             self.increment.emit(i)
    ...         self.finished.emit()
    ...
    >>> worker, thread = new_worker_qthread(
    ...     Worker,
    ...     'argument',
    ...     start=True,
    ...     connections={'increment': print},
    ... )



    >>> print([i for i in example_generator(4)])
    [0, 1, 2, 3]

    """

    if not isinstance(connections, (dict, type(None))):
        raise TypeError('connections parameter must be a dict')

    thread = QThread()
    worker = Worker(*args, **kwargs)
    worker.moveToThread(thread)
    thread.started.connect(worker.work)
    worker.finished.connect(thread.quit)
    worker.finished.connect(worker.deleteLater)
    thread.finished.connect(thread.deleteLater)

    if connections:
        [getattr(worker, key).connect(val) for key, val in connections.items()]

    if start:
        thread.start()  # sometimes need to connect stuff before starting
    return worker, thread
class DownloadBibleMp3Dialog(QDialog):
    def __init__(self, parent):
        super().__init__()

        self.bibles = {
            "BBE (British accent)":
            ("BBE", "otseng/UniqueBible_MP3_BBE_british", "british"),
            "KJV (American accent)":
            ("KJV", "otseng/UniqueBible_MP3_KJV", "default"),
            "KJV (American soft music)":
            ("KJV", "otseng/UniqueBible_MP3_KJV_soft_music", "soft-music"),
            "NHEB (Indian accent)":
            ("NHEB", "otseng/UniqueBible_MP3_NHEB_indian", "indian"),
            "WEB (American accent)": ("WEB", "otseng/UniqueBible_MP3_WEB",
                                      "default"),
            "CUV (Chinese)": ("CUV", "otseng/UniqueBible_MP3_CUV", "default"),
            "HHBD (Hindi)": ("HHBD", "otseng/UniqueBible_MP3_HHBD", "default"),
            "RVA (Spanish)": ("RVA", "otseng/UniqueBible_MP3_RVA", "default"),
            "TR (Modern Greek)": ("TR", "otseng/UniqueBible_MP3_TR", "modern"),
        }
        self.parent = parent
        self.setWindowTitle(config.thisTranslation["gitHubBibleMp3Files"])
        self.setMinimumSize(150, 450)
        self.selectedRendition = None
        self.selectedText = None
        self.selectedRepo = None
        self.selectedDirectory = None
        self.settingBibles = False
        self.thread = None
        self.setupUI()

    def setupUI(self):
        mainLayout = QVBoxLayout()

        title = QLabel(config.thisTranslation["gitHubBibleMp3Files"])
        mainLayout.addWidget(title)

        self.versionsLayout = QVBoxLayout()
        self.renditionsList = QListWidget()
        self.renditionsList.itemClicked.connect(self.selectItem)
        for rendition in self.bibles.keys():
            self.renditionsList.addItem(rendition)
        self.renditionsList.setMaximumHeight(100)
        self.versionsLayout.addWidget(self.renditionsList)
        mainLayout.addLayout(self.versionsLayout)

        self.downloadTable = QTableView()
        self.downloadTable.setEnabled(False)
        self.downloadTable.setFocusPolicy(Qt.StrongFocus)
        self.downloadTable.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.downloadTable.setSortingEnabled(True)
        self.dataViewModel = QStandardItemModel(self.downloadTable)
        self.downloadTable.setModel(self.dataViewModel)
        mainLayout.addWidget(self.downloadTable)

        buttonsLayout = QHBoxLayout()
        selectAllButton = QPushButton(config.thisTranslation["selectAll"])
        selectAllButton.setFocusPolicy(Qt.StrongFocus)
        selectAllButton.clicked.connect(self.selectAll)
        buttonsLayout.addWidget(selectAllButton)
        selectNoneButton = QPushButton(config.thisTranslation["selectNone"])
        selectNoneButton.setFocusPolicy(Qt.StrongFocus)
        selectNoneButton.clicked.connect(self.selectNone)
        buttonsLayout.addWidget(selectNoneButton)
        otButton = QPushButton("1-39")
        otButton.setFocusPolicy(Qt.StrongFocus)
        otButton.clicked.connect(self.selectOT)
        buttonsLayout.addWidget(otButton)
        ntButton = QPushButton("40-66")
        ntButton.setFocusPolicy(Qt.StrongFocus)
        ntButton.clicked.connect(self.selectNT)
        buttonsLayout.addWidget(ntButton)
        # buttonsLayout.addStretch()
        mainLayout.addLayout(buttonsLayout)

        self.downloadButton = QPushButton(config.thisTranslation["download"])
        self.downloadButton.setFocusPolicy(Qt.StrongFocus)
        self.downloadButton.setAutoDefault(True)
        self.downloadButton.setFocus()
        self.downloadButton.clicked.connect(self.download)
        mainLayout.addWidget(self.downloadButton)

        self.status = QLabel("")
        mainLayout.addWidget(self.status)

        buttonLayout = QHBoxLayout()
        self.closeButton = QPushButton(config.thisTranslation["close"])
        self.closeButton.setFocusPolicy(Qt.StrongFocus)
        self.closeButton.clicked.connect(self.closeDialog)
        buttonLayout.addWidget(self.closeButton)
        mainLayout.addLayout(buttonLayout)

        self.setLayout(mainLayout)

        self.renditionsList.item(0).setSelected(True)
        bible = self.renditionsList.item(0).text()
        self.selectRendition(bible)

        self.downloadButton.setDefault(True)
        QTimer.singleShot(0, self.downloadButton.setFocus)

    def selectItem(self, item):
        self.selectRendition(item.text())

    def selectRendition(self, rendition):
        from util.GithubUtil import GithubUtil

        self.selectedRendition = rendition
        self.downloadTable.setEnabled(True)
        self.selectedText, self.selectedRepo, self.selectedDirectory = self.bibles[
            self.selectedRendition]
        self.github = GithubUtil(self.selectedRepo)
        self.repoData = self.github.getRepoData()
        self.settingBibles = True
        self.dataViewModel.clear()
        rowCount = 0
        for file in self.repoData.keys():
            if len(str(file)) > 3:
                engFullBookName = file[3:]
            else:
                engFullBookName = BibleBooks().eng[str(int(file))][1]
            item = QStandardItem(file[:3].strip())
            folder = os.path.join("audio", "bibles", self.selectedText,
                                  self.selectedDirectory, file)
            folderWithName = os.path.join("audio", "bibles", self.selectedText,
                                          self.selectedDirectory,
                                          file + " " + engFullBookName)
            if os.path.exists(folder) or os.path.exists(folderWithName):
                item.setCheckable(False)
                item.setCheckState(Qt.Unchecked)
                item.setEnabled(False)
            else:
                item.setCheckable(True)
                item.setCheckState(Qt.Checked)
                item.setEnabled(True)
            self.dataViewModel.setItem(rowCount, 0, item)
            item = QStandardItem(engFullBookName)
            self.dataViewModel.setItem(rowCount, 1, item)
            if os.path.exists(folder) or os.path.exists(folderWithName):
                item = QStandardItem("Installed")
                self.dataViewModel.setItem(rowCount, 2, item)
            else:
                item = QStandardItem("")
                self.dataViewModel.setItem(rowCount, 2, item)
            rowCount += 1
        self.dataViewModel.setHorizontalHeaderLabels([
            config.thisTranslation["menu_book"],
            config.thisTranslation["name"], ""
        ])
        self.downloadTable.setColumnWidth(0, 90)
        self.downloadTable.setColumnWidth(1, 125)
        self.downloadTable.setColumnWidth(2, 125)
        # self.downloadTable.resizeColumnsToContents()
        self.settingBibles = False

    def selectAll(self):
        for index in range(self.dataViewModel.rowCount()):
            item = self.dataViewModel.item(index)
            if item.isEnabled():
                item.setCheckState(Qt.Checked)

    def selectNone(self):
        for index in range(self.dataViewModel.rowCount()):
            item = self.dataViewModel.item(index)
            item.setCheckState(Qt.Unchecked)

    def selectOT(self):
        for index in range(self.dataViewModel.rowCount()):
            item = self.dataViewModel.item(index)
            bookNum = int(item.text())
            if bookNum <= 39:
                if item.isEnabled():
                    item.setCheckState(Qt.Checked)
                else:
                    item.setCheckState(Qt.Unchecked)
            else:
                item.setCheckState(Qt.Unchecked)

    def selectNT(self):
        for index in range(self.dataViewModel.rowCount()):
            item = self.dataViewModel.item(index)
            bookNum = int(item.text())
            if bookNum >= 40:
                if item.isEnabled():
                    item.setCheckState(Qt.Checked)
                else:
                    item.setCheckState(Qt.Unchecked)
            else:
                item.setCheckState(Qt.Unchecked)

    def download(self):
        self.downloadButton.setEnabled(False)
        self.setStatus(config.thisTranslation["message_installing"])
        self.closeButton.setEnabled(False)
        folder = os.path.join("audio", "bibles")
        if not os.path.exists(folder):
            os.mkdir(folder)
        folder = os.path.join("audio", "bibles", self.selectedText)
        if not os.path.exists(folder):
            os.mkdir(folder)
        folder = os.path.join("audio", "bibles", self.selectedText,
                              self.selectedDirectory)
        if not os.path.exists(folder):
            os.mkdir(folder)
        self.thread = QThread()
        self.worker = DownloadFromGitHub(self.github, self.repoData,
                                         self.dataViewModel, self.selectedText,
                                         self.selectedDirectory)
        self.worker.moveToThread(self.thread)
        self.thread.started.connect(self.worker.run)
        self.worker.finished.connect(self.thread.quit)
        self.worker.finished.connect(self.worker.deleteLater)
        self.thread.finished.connect(self.worker.deleteLater)
        self.worker.finished.connect(self.finishedDownloading)
        self.worker.progress.connect(self.setStatus)
        self.thread.start()

    def finishedDownloading(self, count):
        self.selectRendition(self.selectedRendition)
        self.setStatus("")
        self.downloadButton.setEnabled(True)
        self.closeButton.setEnabled(True)
        if count > 0:
            self.parent.displayMessage(
                config.thisTranslation["message_installed"])

    def setStatus(self, message):
        self.status.setText(message)
        QApplication.processEvents()

    def closeDialog(self):
        if self.thread:
            if self.thread.isRunning():
                self.thread.quit()
        self.close()
Beispiel #26
0
class CondaPackagesWidget(QWidget):
    """Conda Packages Widget."""
    # Location of updated repo.json files from continuum/binstar
    CONDA_CONF_PATH = get_conf_path('repo')

    # Location of continuum/anaconda default repos shipped with conda-manager
    DATA_PATH = get_module_data_path()

    # file inside DATA_PATH with metadata for conda packages
    DATABASE_FILE = 'packages.ini'

    sig_worker_ready = Signal()
    sig_packages_ready = Signal()
    sig_environment_created = Signal()

    def __init__(self, parent, name=None, prefix=None, channels=[]):
        super(CondaPackagesWidget, self).__init__(parent)
        self._parent = parent
        self._status = ''  # Statusbar message
        self._conda_process = conda_api_q.CondaProcess(self)
        self._root_prefix = self._conda_process.ROOT_PREFIX
        self._prefix = None
        self._temporal_action_dic = {}
        self._download_manager = DownloadManager(self,
                                                 self._on_download_finished,
                                                 self._on_download_progress,
                                                 self.CONDA_CONF_PATH)
        self._thread = QThread(self)
        self._worker = None
        self._db_metadata = cp.ConfigParser()
        self._db_file = CondaPackagesWidget.DATABASE_FILE
        self._db_metadata.readfp(open(osp.join(self.DATA_PATH, self._db_file)))
        self._packages_names = None
        self._row_data = None
        self._hide_widgets = False

        # TODO: Hardcoded channels for the moment
        self._default_channels = [
            ['_free_', 'http://repo.continuum.io/pkgs/free'],
            #['_pro_', 'http://repo.continuum.io/pkgs/pro']
            ]

        self._extra_channels = channels
        # pyqt not working with ssl some bug here on the anaconda compilation
        # [['binstar_goanpeca_', 'https://conda.binstar.org/goanpeca']]

        self._repo_name = None   # linux-64, win-32, etc...
        self._channels = None    # [['filename', 'channel url'], ...]
        self._repo_files = None  # [filepath, filepath, ...]
        self._packages = {}
        self._download_error = None
        self._error = None

        # Widgets
        self.combobox_filter = QComboBox(self)
        self.button_update = QPushButton(_('Update package index'))
        self.textbox_search = SearchLineEdit(self)

        self.table = CondaPackagesTable(self)
        self.status_bar = QLabel(self)
        self.progress_bar = QProgressBar(self)

        self.button_ok = QPushButton(_('Ok'))

        self.bbox = QDialogButtonBox(Qt.Horizontal)
        self.bbox.addButton(self.button_ok, QDialogButtonBox.ActionRole)

        self.widgets = [self.button_update, self.combobox_filter,
                        self.textbox_search, self.table, self.button_ok]

        # Widgets setup
        self.combobox_filter.addItems([k for k in
                                       const.COMBOBOX_VALUES_ORDERED])
        self.combobox_filter.setMinimumWidth(120)
        self.button_ok.setDefault(True)
        self.button_ok.setAutoDefault(True)
        self.button_ok.setVisible(False)

        self.progress_bar.setVisible(False)
        self.progress_bar.setTextVisible(False)
        self.progress_bar.setMaximumHeight(16)
        self.progress_bar.setMaximumWidth(130)

        self.setWindowTitle(_("Conda Package Manager"))
        self.setMinimumSize(QSize(480, 300))

        # Signals and slots
        self.combobox_filter.currentIndexChanged.connect(self.filter_package)
        self.button_update.clicked.connect(self.update_package_index)
        self.textbox_search.textChanged.connect(self.search_package)
        self._conda_process.sig_partial.connect(self._on_conda_process_partial)
        self._conda_process.sig_finished.connect(self._on_conda_process_ready)

        # NOTE: do not try to save the QSpacerItems in a variable for reuse
        # it will crash python on exit if you do!

        # Layout
        self._spacer_w = 250
        self._spacer_h = 5

        self._top_layout = QHBoxLayout()
        self._top_layout.addWidget(self.combobox_filter)
        self._top_layout.addWidget(self.button_update)
        self._top_layout.addWidget(self.textbox_search)

        self._middle_layout = QVBoxLayout()
        self._middle_layout.addWidget(self.table)

        self._bottom_layout = QHBoxLayout()
        self._bottom_layout.addWidget(self.status_bar, Qt.AlignLeft)
        self._bottom_layout.addWidget(self.progress_bar, Qt.AlignRight)

        self._layout = QVBoxLayout(self)
        self._layout.addItem(QSpacerItem(self._spacer_w, self._spacer_h))
        self._layout.addLayout(self._top_layout)
        self._layout.addLayout(self._middle_layout)
        self._layout.addItem(QSpacerItem(self._spacer_w, self._spacer_h))
        self._layout.addLayout(self._bottom_layout)
        self._layout.addItem(QSpacerItem(self._spacer_w, self._spacer_h))
        self._layout.addWidget(self.bbox)
        self._layout.addItem(QSpacerItem(self._spacer_w, self._spacer_h))

        self.setLayout(self._layout)

        # Setup
        self.set_environment(name=name, prefix=prefix, update=False)
        if self._supports_architecture():
            self.update_package_index()
        else:
            status = _('no packages supported for this architecture!')
            self._update_status(progress=[0, 0], hide=True, status=status)

    def _supports_architecture(self):
        """ """
        self._set_repo_name()

        if self._repo_name is None:
            return False
        else:
            return True

    def _set_repo_name(self):
        """Get python system and bitness, and return default repo name"""
        system = sys.platform.lower()
        bitness = 64 if sys.maxsize > 2**32 else 32
        machine = platform.machine()
        fname = [None, None]

        if 'win' in system:
            fname[0] = 'win'
        elif 'lin' in system:
            fname[0] = 'linux'
        elif 'osx' in system or 'darwin' in system:  # TODO: is this correct?
            fname[0] = 'osx'
        else:
            return None

        if bitness == 32:
            fname[1] = '32'
        elif bitness == 64:
            fname[1] = '64'
        else:
            return None

        # armv6l
        if machine.startswith('armv6'):
            fname[1] = 'armv6l'

        if None in fname:
            self._repo_name = None
        else:
            self._repo_name = '-'.join(fname)

    def _set_channels(self):
        """ """
        default = self._default_channels
        extra = self._extra_channels
        body = self._repo_name
        tail = '/repodata.json'
        channels = []
        files = []

        for channel in default + extra:
            prefix = channel[0]
            url = '{0}/{1}{2}'.format(channel[1], body, tail)
            name = '{0}{1}.json'.format(prefix, body)
            channels.append([name, url])
            files.append(osp.join(self.CONDA_CONF_PATH, name))

        self._repo_files = files
        self._channels = channels

    def _download_repodata(self):
        """download the latest version available of the repo(s)"""
        status = _('Updating package index...')
        self._update_status(hide=True, progress=[0, 0], status=status)

        self._download_manager.set_queue(self._channels)
        self._download_manager.start_download()

    # --- Callback download manager
    # ------------------------------------------------------------------------
    def _on_download_progress(self, progress):
        """function called by download manager when receiving data

        progress : [int, int]
            A two item list of integers with relating [downloaded, total]
        """
        self._update_status(hide=True, progress=progress, status=None)

    def _on_download_finished(self):
        """function called by download manager when finished all downloads

        this will be called even if errors were encountered, so error handling
        is done here as well
        """
        error = self._download_manager.get_errors()

        if error is not None:
            self._update_status(hide=False)

            if not osp.isdir(self.CONDA_CONF_PATH):
                os.mkdir(self.CONDA_CONF_PATH)

            for repo_file in self._repo_files:
                # if a file does not exists, look for one in DATA_PATH
                if not osp.isfile(repo_file):
                    filename = osp.basename(repo_file)
                    bck_repo_file = osp.join(self.DATA_PATH, filename)

                    # if available copy to CONDA_CONF_PATH
                    if osp.isfile(bck_repo_file):
                        shutil.copy(bck_repo_file, repo_file)
                    # otherwise remove from the repo_files list
                    else:
                        self._repo_files.remove(repo_file)
            self._error = None

        self.setup_packages()

    # ------------------------------------------------------------------------
    def setup_packages(self):
        """ """
        pip_packages = self._conda_process.pip_list(prefix=self._prefix)
        self._thread.terminate()
        self._thread = QThread(self)
        self._worker = PackagesWorker(self, self._repo_files,
                                      self._prefix, self._root_prefix,
                                      pip_packages)
        self._worker.sig_status_updated.connect(self._update_status)
        self._worker.sig_ready.connect(self._worker_ready)
        self._worker.sig_ready.connect(self._thread.quit)
        self._worker.moveToThread(self._thread)

        self._thread.started.connect(self._worker._prepare_model)
        self._thread.start()

    def _worker_ready(self):
        """ """
        self._packages_names = self._worker.packages_names
        self._packages_versions = self._worker.packages_versions
        self._row_data = self._worker.row_data

        # depending on the size of table this might lock the gui for a moment
        self.table.setup_model(self._packages_names, self._packages_versions,
                               self._row_data)
        self.table.filter_changed()

        self._update_status(hide=False)
        self.filter_package(const.INSTALLED)
        self.sig_worker_ready.emit()
        self.sig_packages_ready.emit()

    def _update_status(self, status=None, hide=True, progress=None, env=False):
        """Update status bar, progress bar display and widget visibility

        status : str
            TODO:
        hide : bool
            TODO:
        progress : [int, int]
            TODO:
        """
        self.busy = hide
        for widget in self.widgets:
            widget.setDisabled(hide)

        self.progress_bar.setVisible(hide)

        if status is not None:
            self._status = status

        if self._prefix == self._root_prefix:
            short_env = 'root'
        elif self._conda_process.environment_exists(prefix=self._prefix):
            short_env = osp.basename(self._prefix)
        else:
            short_env = self._prefix

        if env:
            self._status = '{0} (<b>{1}</b>)'.format(self._status,
                                                     short_env)
        self.status_bar.setText(self._status)

        if progress is not None:
            self.progress_bar.setMinimum(0)
            self.progress_bar.setMaximum(progress[1])
            self.progress_bar.setValue(progress[0])

    def _run_action(self, package_name, action, version, versions):
        """ """
        prefix = self._prefix
        dlg = CondaPackageActionDialog(self, prefix, package_name, action,
                                       version, versions)

        if dlg.exec_():
            dic = {}

            self.status = 'Processing'
            self._update_status(hide=True)
            self.repaint()

            ver1 = dlg.label_version.text()
            ver2 = dlg.combobox_version.currentText()
            pkg = u'{0}={1}{2}'.format(package_name, ver1, ver2)
            dep = dlg.checkbox.checkState()
            state = dlg.checkbox.isEnabled()
            dlg.close()

            dic['pkg'] = pkg
            dic['dep'] = not (dep == 0 and state)
            dic['action'] = None
            self._run_conda_process(action, dic)

    def _run_conda_process(self, action, dic):
        """ """
        cp = self._conda_process
        prefix = self._prefix

        if prefix == self._root_prefix:
            name = 'root'
        elif self._conda_process.environment_exists(prefix=prefix):
            name = osp.basename(prefix)
        else:
            name = prefix

        if 'pkg' in dic and 'dep' in dic:
            pkgs = dic['pkg']
            if not isinstance(pkgs, list):
                pkgs = [pkgs]
            dep = dic['dep']

        if action == const.INSTALL or action == const.UPGRADE or \
           action == const.DOWNGRADE:
            status = _('Installing <b>') + dic['pkg'] + '</b>'
            status = status + _(' into <i>') + name + '</i>'
            cp.install(prefix=prefix, pkgs=pkgs, dep=dep)
        elif action == const.REMOVE:
            status = (_('moving <b>') + dic['pkg'] + '</b>' +
                      _(' from <i>') + name + '</i>')
            cp.remove(pkgs[0], prefix=prefix)

        # --- Actions to be implemented in case of environment needs
        elif action == const.CREATE:
            status = _('Creating environment <b>') + name + '</b>'
            cp.create(prefix=prefix, pkgs=pkgs)
        elif action == const.CLONE:
            status = (_('Cloning ') + '<i>' + dic['cloned from'] +
                      _('</i> into <b>') + name + '</b>')
        elif action == const.REMOVE_ENV:
            status = _('Removing environment <b>') + name + '</b>'

        self._update_status(hide=True, status=status, progress=[0, 0])
        self._temporal_action_dic = dic

    def _on_conda_process_ready(self):
        """ """
        error = self._conda_process.error

        if error is None:
            status = _('there was an error')
            self._update_status(hide=False, status=status)
        else:
            self._update_status(hide=True)

        dic = self._temporal_action_dic
        if dic['action'] == const.CREATE:
            self.sig_environment_created.emit()
        self.setup_packages()

    def _on_conda_process_partial(self):
        """ """
        try:
            partial = self._conda_process.partial.split('\n')[0]
            partial = json.loads(partial)
        except:
            partial = {'progress': 0, 'maxval': 0}

        progress = partial['progress']
        maxval = partial['maxval']

        if 'fetch' in partial:
            status = _('Downloading <b>') + partial['fetch'] + '</b>'
        elif 'name' in partial:
            status = _('Installing and linking <b>') + partial['name'] + '</b>'
        else:
            progress = 0
            maxval = 0
            status = None

        self._update_status(status=status, progress=[progress, maxval])

    # Public api
    # ----------
    def get_package_metadata(self, name):
        """ """
        db = self._db_metadata
        metadata = dict(description='', url='', pypi='', home='', docs='',
                        dev='')
        for key in metadata:
            name_lower = name.lower()
            for name_key in (name_lower, name_lower.split('-')[0]):
                try:
                    metadata[key] = db.get(name_key, key)
                    break
                except (cp.NoSectionError, cp.NoOptionError):
                    pass
        return metadata

    def update_package_index(self):
        """ """
        self._set_channels()
        self._download_repodata()

    def search_package(self, text):
        """ """
        self.table.search_string_changed(text)

    def filter_package(self, value):
        """ """
        self.table.filter_status_changed(value)

    def set_environment(self, name=None, prefix=None, update=True):
        """ """
        if name and prefix:
            raise Exception('#TODO:')

        if name and self._conda_process.environment_exists(name=name):
            self._prefix = self.get_prefix_envname(name)
        elif prefix and self._conda_process.environment_exists(prefix=prefix):
            self._prefix = prefix
        else:
            self._prefix = self._root_prefix

        # Reset environent to reflect this environment in the package model
        if update:
            self.setup_packages()

    def get_environment_prefix(self):
        """Returns the active environment prefix."""
        return self._prefix

    def get_environment_name(self):
        """
        Returns the active environment name if it is located in the default
        conda environments directory, otherwise it returns the prefix.
        """
        name = osp.basename(self._prefix)

        if not (name and self._conda_process.environment_exists(name=name)):
            name = self._prefix

        return name

    def get_environments(self):
        """
        Get a list of conda environments located in the default conda
        environments directory.
        """
        return self._conda_process.get_envs()

    def get_prefix_envname(self, name):
        """Returns the prefix for a given environment by name."""
        return self._conda_process.get_prefix_envname(name)

    def get_package_versions(self, name):
        """ """
        return self.table.source_model.get_package_versions(name)

    def create_environment(self, name=None, prefix=None, packages=['python']):
        """ """
        # If environment exists already? GUI should take care of this
        # BUT the api call should simply set that env as the env
        dic = {}
        dic['name'] = name
        dic['pkg'] = packages
        dic['dep'] = True  # Not really needed but for the moment!
        dic['action'] = const.CREATE
        self._run_conda_process(const.CREATE, dic)

    def enable_widgets(self):
        """ """
        self.table.hide_columns()

    def disable_widgets(self):
        """ """
        self.table.hide_action_columns()
Beispiel #27
0
def _new_worker_qthread(
    Worker: Type[QObject],
    *args,
    _start_thread: bool = False,
    _connect: Dict[str, Callable] = None,
    **kwargs,
):
    """This is a convenience function to start a worker in a Qthread.

    In most cases, the @thread_worker decorator is sufficient and preferable.
    But this allows the user to completely customize the Worker object.
    However, they must then maintain control over the thread and clean up
    appropriately.

    It follows the pattern described here:
    https://www.qt.io/blog/2010/06/17/youre-doing-it-wrong
    and
    https://doc.qt.io/qt-5/qthread.html#details

    see also:
    https://mayaposch.wordpress.com/2011/11/01/how-to-really-truly-use-qthreads-the-full-explanation/

    A QThread object is not a thread! It should be thought of as a class to
    *manage* a thread, not as the actual code or object that runs in that
    thread.  The QThread object is created on the main thread and lives there.

    Worker objects which derive from QObject are the things that actually do
    the work. They can be moved to a QThread as is done here.

    .. note:: Mostly ignorable detail

        While the signals/slots syntax of the worker looks very similar to
        standard "single-threaded" signals & slots, note that inter-thread
        signals and slots (automatically) use an event-based QueuedConnection,
        while intra-thread signals use a DirectConnection. See `Signals and
        Slots Across Threads
        <https://doc.qt.io/qt-5/threads-qobject.html#signals-and-slots-across-threads>`_

    Parameters
    ----------
    Worker : QObject
        QObject type that implements a work() method.  The Worker should also
        emit a finished signal when the work is done.
    start_thread : bool
        If True, thread will be started immediately, otherwise, thread must
        be manually started with thread.start().
    connections: dict, optional
        Optional dictionary of {signal: function} to connect to the new worker.
        for instance:  connections = {'incremented': myfunc} will result in:
        worker.incremented.connect(myfunc)
    *args
        will be passed to the Worker class on instantiation.
    **kwargs
        will be passed to the Worker class on instantiation.

    Returns
    -------
    worker : WorkerBase
        The created worker.
    thread : QThread
        The thread on which the worker is running.

    Examples
    --------
    Create some QObject that has a long-running work method:

    .. code-block:: python

        class Worker(QObject):

            finished = Signal()
            increment = Signal(int)

            def __init__(self, argument):
                super().__init__()
                self.argument = argument

            @Slot()
            def work(self):
                # some long running task...
                import time
                for i in range(10):
                    time.sleep(1)
                    self.increment.emit(i)
                self.finished.emit()

        worker, thread = _new_worker_qthread(
            Worker,
            'argument',
            start_thread=True,
            connections={'increment': print},
        )

    """

    if _connect and not isinstance(_connect, dict):
        raise TypeError('_connect parameter must be a dict')

    thread = QThread()
    worker = Worker(*args, **kwargs)
    worker.moveToThread(thread)
    thread.started.connect(worker.work)
    worker.finished.connect(thread.quit)
    worker.finished.connect(worker.deleteLater)
    thread.finished.connect(thread.deleteLater)

    if _connect:
        [getattr(worker, key).connect(val) for key, val in _connect.items()]

    if _start_thread:
        thread.start()  # sometimes need to connect stuff before starting
    return worker, thread
Beispiel #28
0
class SnippetsActor(QObject):
    #: Signal emitted when the Thread is ready
    sig_snippets_ready = Signal()
    sig_snippets_response = Signal(int, dict)
    sig_update_snippets = Signal(dict)
    sig_mailbox = Signal(dict)

    def __init__(self, parent):
        QObject.__init__(self)
        self.stopped = False
        self.daemon = True
        self.mutex = QMutex()
        self.language_snippets = {}
        self.thread = QThread()
        self.moveToThread(self.thread)

        self.thread.started.connect(self.started)
        self.sig_mailbox.connect(self.handle_msg)
        self.sig_update_snippets.connect(self.update_snippets)

    def stop(self):
        """Stop actor."""
        with QMutexLocker(self.mutex):
            logger.debug("Snippets plugin stopping...")
            self.thread.quit()

    def start(self):
        """Start thread."""
        self.thread.start()

    def started(self):
        """Thread started."""
        logger.debug('Snippets plugin starting...')
        self.sig_snippets_ready.emit()

    @Slot(dict)
    def update_snippets(self, snippets):
        """Update available snippets."""
        logger.debug('Updating snippets...')
        for language in snippets:
            lang_snippets = snippets[language]
            lang_trie = Trie()
            for trigger in lang_snippets:
                trigger_descriptions = lang_snippets[trigger]
                lang_trie[trigger] = (trigger, trigger_descriptions)
            self.language_snippets[language] = lang_trie

    @Slot(dict)
    def handle_msg(self, message):
        """Handle one message"""
        msg_type, _id, file, msg = [
            message[k] for k in ('type', 'id', 'file', 'msg')]
        logger.debug(u'Perform request {0} with id {1}'.format(msg_type, _id))
        if msg_type == CompletionRequestTypes.DOCUMENT_COMPLETION:
            language = msg['language']
            current_word = msg['current_word']
            snippets = []

            if current_word is None:
                snippets = {'params': snippets}
                self.sig_snippets_response.emit(_id, snippets)
                return

            if language in self.language_snippets:
                language_snippets = self.language_snippets[language]
                if language_snippets[current_word]:
                    for node in language_snippets[current_word]:
                        trigger, info = node.value
                        for description in info:
                            description_snippet = info[description]
                            text = description_snippet['text']
                            remove_trigger = description_snippet[
                                'remove_trigger']
                            snippets.append({
                                'kind': CompletionItemKind.SNIPPET,
                                'insertText': text,
                                'label': f'{trigger} ({description})',
                                'sortText': f'zzz{trigger}',
                                'filterText': trigger,
                                'documentation': '',
                                'provider': SNIPPETS_COMPLETION,
                                'remove_trigger': remove_trigger
                            })

            snippets = {'params': snippets}
            self.sig_snippets_response.emit(_id, snippets)
Beispiel #29
0
class FallbackActor(QObject):
    #: Signal emitted when the Thread is ready
    sig_fallback_ready = Signal()
    sig_set_tokens = Signal(int, dict)
    sig_mailbox = Signal(dict)

    def __init__(self, parent):
        QObject.__init__(self)
        self.stopped = False
        self.daemon = True
        self.mutex = QMutex()
        self.file_tokens = {}
        self.diff_patch = diff_match_patch()
        self.thread = QThread()
        self.moveToThread(self.thread)

        self.thread.started.connect(self.started)
        self.sig_mailbox.connect(self.handle_msg)

    def tokenize(self, text, offset, language, current_word):
        """
        Return all tokens in `text` and all keywords associated by
        Pygments to `language`.
        """
        valid = is_prefix_valid(text, offset, language)
        if not valid:
            return []

        # Get language keywords provided by Pygments
        try:
            lexer = get_lexer_by_name(language)
            keywords = get_keywords(lexer)
        except Exception:
            keywords = []
        keyword_set = set(keywords)
        keywords = [{
            'kind': CompletionItemKind.KEYWORD,
            'insertText': keyword,
            'label': keyword,
            'sortText': keyword,
            'filterText': keyword,
            'documentation': '',
            'provider': FALLBACK_COMPLETION
        } for keyword in keywords]

        # Get file tokens
        tokens = get_words(text, offset, language)
        tokens = [{
            'kind': CompletionItemKind.TEXT,
            'insertText': token,
            'label': token,
            'sortText': token,
            'filterText': token,
            'documentation': '',
            'provider': FALLBACK_COMPLETION
        } for token in tokens]
        for token in tokens:
            if token['insertText'] not in keyword_set:
                keywords.append(token)

        # Filter matching results
        if current_word is not None:
            current_word = current_word.lower()
            keywords = [
                k for k in keywords if current_word in k['insertText'].lower()
            ]

        return keywords

    def stop(self):
        """Stop actor."""
        with QMutexLocker(self.mutex):
            logger.debug("Fallback plugin stopping...")
            self.thread.quit()

    def start(self):
        """Start thread."""
        self.thread.start()

    def started(self):
        """Thread started."""
        logger.debug('Fallback plugin starting...')
        self.sig_fallback_ready.emit()

    @Slot(dict)
    def handle_msg(self, message):
        """Handle one message"""
        msg_type, _id, file, msg = [
            message[k] for k in ('type', 'id', 'file', 'msg')
        ]
        logger.debug(u'Perform request {0} with id {1}'.format(msg_type, _id))
        if msg_type == LSPRequestTypes.DOCUMENT_DID_OPEN:
            self.file_tokens[file] = {
                'text': msg['text'],
                'offset': msg['offset'],
                'language': msg['language'],
            }
        elif msg_type == LSPRequestTypes.DOCUMENT_DID_CHANGE:
            if file not in self.file_tokens:
                self.file_tokens[file] = {
                    'text': '',
                    'offset': msg['offset'],
                    'language': msg['language'],
                }
            diff = msg['diff']
            text = self.file_tokens[file]
            text['offset'] = msg['offset']
            text, _ = self.diff_patch.patch_apply(diff, text['text'])
            self.file_tokens[file]['text'] = text
        elif msg_type == LSPRequestTypes.DOCUMENT_DID_CLOSE:
            self.file_tokens.pop(file, {})
        elif msg_type == LSPRequestTypes.DOCUMENT_COMPLETION:
            tokens = []
            if file in self.file_tokens:
                text_info = self.file_tokens[file]
                tokens = self.tokenize(text_info['text'], text_info['offset'],
                                       text_info['language'],
                                       msg['current_word'])
            tokens = {'params': tokens}
            self.sig_set_tokens.emit(_id, tokens)
Beispiel #30
0
class MainWindow(QWidget):
    def __init__(self, config: Config) -> None:
        """
        Main window with the GUI and whatever player is being used.
        """

        super().__init__()
        self.setWindowTitle('vidify')

        # Setting the window to stay on top
        if config.stay_on_top:
            self.setWindowFlags(Qt.WindowStaysOnTopHint)

        # Setting the fullscreen and window size
        if config.fullscreen:
            self.showFullScreen()
        else:
            self.resize(config.width or 800, config.height or 600)

        # Loading the used fonts (Inter)
        font_db = QFontDatabase()
        for font in Res.fonts:
            font_db.addApplicationFont(font)

        # Initializing the player and saving the config object in the window.
        self.layout = QHBoxLayout(self)
        self.layout.setContentsMargins(0, 0, 0, 0)
        self.layout.setSpacing(0)
        self.player = initialize_player(config.player, config)
        logging.info("Using %s as the player", config.player)
        self.config = config

        # The API initialization is more complex. For more details, please
        # check the flow diagram in vidify.api. First we have to check if
        # the API is saved in the config:
        try:
            api_data = get_api_data(config.api)
        except KeyError:
            # Otherwise, the user is prompted for an API. After choosing one,
            # it will be initialized from outside this function.
            logging.info("API not found: prompting the user")
            self.API_selection = APISelection()
            self.layout.addWidget(self.API_selection)
            self.API_selection.api_chosen.connect(self.on_api_selection)
        else:
            logging.info("Using %s as the API", config.api)
            self.initialize_api(api_data)

    @Slot(str)
    def on_api_selection(self, api_str: str) -> None:
        """
        Method called when the API is selected with APISelection.
        The provided api string must be an existent entry
        inside the APIData enumeration.
        """

        # Removing the widget used to obtain the API string
        self.layout.removeWidget(self.API_selection)
        self.API_selection.setParent(None)
        self.API_selection.hide()
        del self.API_selection

        # Saving the API in the config
        self.config.api = api_str

        # Starting the API initialization
        self.initialize_api(APIData[api_str])

    def initialize_api(self, api_data: APIData) -> None:
        """
        Initializes an API with the information from APIData.
        """

        # The API may need interaction with the user to obtain credentials
        # or similar data. This function will already take care of the
        # rest of the initialization.
        if api_data.gui_init_fn is not None:
            fn = getattr(self, api_data.gui_init_fn)
            fn()
            return

        # Initializing the API with dependency injection.
        mod = importlib.import_module(api_data.module)
        cls = getattr(mod, api_data.class_name)
        self.api = cls()

        self.wait_for_connection(
            self.api.connect_api, message=api_data.connect_msg,
            event_loop_interval=api_data.event_loop_interval)

    def wait_for_connection(self, conn_fn: Callable[[], None],
                            message: Optional[str] = None,
                            event_loop_interval: int = 1000) -> None:

        """
        Creates an APIConnecter instance and waits for the API to be
        available, or times out otherwise.
        """

        self.event_loop_interval = event_loop_interval
        self.api_connecter = APIConnecter(
            conn_fn, message or "Waiting for connection")
        self.api_connecter.success.connect(self.on_conn_success)
        self.api_connecter.fail.connect(self.on_conn_fail)
        self.layout.addWidget(self.api_connecter)
        self.api_connecter.start()

    @Slot()
    def on_conn_fail(self) -> None:
        """
        If the API failed to connect, the app will be closed.
        """

        print("Timed out waiting for the connection")
        QCoreApplication.exit(1)

    @Slot(float)
    def on_conn_success(self, start_time: float) -> None:
        """
        Once the connection has been established correctly, the API can
        be started properly.
        """

        logging.info("Succesfully connected to the API")
        self.layout.removeWidget(self.api_connecter)
        del self.api_connecter

        # Initializing the optional audio synchronization extension, now
        # that there's access to the API's data. Note that this feature
        # is only available on Linux.
        if self.config.audiosync:
            from vidify.audiosync import AudiosyncWorker
            self.audiosync = AudiosyncWorker(self.api.player_name)
            self.audiosync.success.connect(self.on_audiosync_success)
            self.audiosync.failed.connect(self.on_audiosync_fail)

        # Loading the player
        self.setStyleSheet(f"background-color:{Colors.black};")
        self.layout.addWidget(self.player)
        self.play_video(self.api.artist, self.api.title, start_time)

        # Connecting to the signals generated by the API
        self.api.new_song_signal.connect(self.play_video)
        self.api.position_signal.connect(self.change_video_position)
        self.api.status_signal.connect(self.change_video_status)

        # Starting the event loop if it was initially passed as
        # a parameter.
        if self.event_loop_interval is not None:
            self.start_event_loop(self.api.event_loop,
                                  self.event_loop_interval)

    def start_event_loop(self, event_loop: Callable[[], None],
                         ms: int) -> None:
        """
        Starts a "manual" event loop with a timer every `ms` milliseconds.
        This is used with the SwSpotify API and the Web API to check every
        `ms` seconds if a change has happened, like if the song was paused.
        """

        logging.info("Starting event loop")
        timer = QTimer(self)

        # Qt doesn't accept a method as the parameter so it's converted
        # to a function.
        if isinstance(event_loop, types.MethodType):
            timer.timeout.connect(lambda: event_loop())
        else:
            timer.timeout.connect(event_loop)
        timer.start(ms)

    @Slot(bool)
    def change_video_status(self, is_playing: bool) -> None:
        """
        Slot used for API updates of the video status.
        """

        self.player.pause = not is_playing

        # If there is an audiosync thread running, this will pause the sound
        # recording and youtube downloading.
        if self.config.audiosync and self.audiosync.status != 'idle':
            self.audiosync.is_running = is_playing

    @Slot(int)
    def change_video_position(self, ms: int) -> None:
        """
        Slot used for API updates of the video position.
        """

        if not self.config.audiosync:
            self.player.position = ms

        # Audiosync is aborted if the position of the video changed, since
        # the audio being recorded won't make sense.
        if self.config.audiosync and self.audiosync.status != 'idle':
            self.audiosync.abort()

    @Slot(str, str, float)
    def play_video(self, artist: str, title: str, start_time: float) -> None:
        """
        Slot used to play a video. This is called when the API is first
        initialized from this GUI, and afterwards from the event loop handler
        whenever a new song is detected.

        If an error was detected when downloading the video, the default one
        is shown instead.

        Both audiosync and youtubedl work in separate threads to avoid
        blocking the GUI. This method will start both of them.
        """

        # Checking that the artist and title are valid first of all
        if self.api.artist in (None, '') and self.api.title in (None, ''):
            logging.info("The provided artist and title are empty.")
            self.on_youtubedl_fail()
            if self.config.audiosync:
                self.on_audiosync_fail()
            return

        # This delay is used to know the elapsed time until the video
        # actually starts playing, used in the audiosync feature.
        self.timestamp = start_time
        query = f"ytsearch:{format_name(artist, title)} Official Video"

        if self.config.audiosync:
            self.launch_audiosync(query)

        self.launch_youtubedl(query)

    def launch_audiosync(self, query: str) -> None:
        """
        Starts the audiosync thread, that will call either
        self.on_audiosync_success, or self.on_audiosync_fail once it's
        finished.

        First trying to stop the previous audiosync thread, as only
        one audiosync thread can be running at once.

        Note: QThread.start() is guaranteed to work once QThread.run()
        has returned. Thus, this will wait until it's done and launch
        the new one.
        """

        self.audiosync.abort()
        self.audiosync.wait()
        self.audiosync.youtube_title = query
        self.audiosync.start()
        logging.info("Started a new audiosync job")

    def launch_youtubedl(self, query: str) -> None:
        """
        Starts a YoutubeDL thread that will call either
        self.on_youtubedl_success or self.on_youtubedl_fail once it's done.
        """

        logging.info("Starting the youtube-dl thread")
        self.youtubedl = YouTubeDLWorker(
            query, self.config.debug, self.config.width, self.config.height)
        self.yt_thread = QThread()
        self.youtubedl.moveToThread(self.yt_thread)
        self.yt_thread.started.connect(self.youtubedl.get_url)
        self.youtubedl.success.connect(self.on_yt_success)
        self.youtubedl.fail.connect(self.on_youtubedl_fail)
        self.youtubedl.finish.connect(self.yt_thread.exit)
        self.yt_thread.start()

    @Slot()
    def on_youtubedl_fail(self) -> None:
        """
        If Youtube-dl for whatever reason failed to load the video, a fallback
        error video is shown, along with a message to let the user know what
        happened.
        """

        self.player.start_video(Res.default_video, self.api.is_playing)
        print("The video wasn't found, either because of an issue with your"
              " internet connection or because the provided data was invalid."
              " For more information, enable the debug mode.")

    @Slot(str)
    def on_yt_success(self, url: str) -> None:
        """
        Obtains the video URL from the Youtube-dl thread and starts playing
        the video. Also shows the lyrics if enabled. The position of the video
        isn't set if it's using audiosync, because this is done by the
        AudiosyncWorker thread.
        """

        self.player.start_video(url, self.api.is_playing)

        if not self.config.audiosync:
            try:
                self.player.position = self.api.position
            except NotImplementedError:
                self.player.position = 0

        # Finally, the lyrics are displayed. If the video wasn't found, an
        # error message is shown.
        if self.config.lyrics:
            print(get_lyrics(self.api.artist, self.api.title))

    @Slot()
    def on_audiosync_fail(self) -> None:
        """
        Currently, when audiosync fails, nothing happens.
        """

        logging.info("Audiosync module failed to return the lag")

    @Slot(int)
    def on_audiosync_success(self, lag: int) -> None:
        """
        Slot used after the audiosync function has finished. It sets the
        returned lag in milliseconds on the player.

        This assumes that the song wasn't paused until this issue is fixed:
        https://github.com/vidify/audiosync/issues/12
        """

        logging.info("Audiosync module returned %d ms", lag)

        # The current API position according to what's being recorded.
        playback_delay = round((time.time() - self.timestamp) * 1000) \
            - self.player.position
        lag += playback_delay

        # The user's custom audiosync delay. This is basically the time taken
        # until the module started recording (which may depend on the user
        # hardware and other things). Thus, it will almost always be a
        # negative value.
        lag += self.config.audiosync_calibration

        logging.info("Total delay is %d ms", lag)
        if lag > 0:
            self.player.position += lag
        elif lag < 0:
            # If a negative delay is larger than the current player position,
            # the player position is set to zero after the lag has passed
            # with a timer.
            if self.player.position < -lag:
                self.sync_timer = QTimer(self)
                self.sync_timer.singleShot(
                    -lag, lambda: self.change_video_position(0))
            else:
                self.player.position += lag

    def init_spotify_web_api(self) -> None:
        """
        SPOTIFY WEB API CUSTOM FUNCTION

        Note: the Tekore imports are done inside the functions so that
        Tekore isn't needed for whoever doesn't plan to use the Spotify
        Web API.
        """

        from vidify.api.spotify.web import get_token
        from vidify.gui.api.spotify_web import SpotifyWebPrompt

        token = get_token(self.config.refresh_token, self.config.client_id,
                          self.config.client_secret)

        if token is not None:
            # If the previous token was valid, the API can already start.
            logging.info("Reusing a previously generated token")
            self.start_spotify_web_api(token, save_config=False)
        else:
            # Otherwise, the credentials are obtained with the GUI. When
            # a valid auth token is ready, the GUI will initialize the API
            # automatically exactly like above. The GUI won't ask for a
            # redirect URI for now.
            logging.info("Asking the user for credentials")
            # The SpotifyWebPrompt handles the interaction with the user and
            # emits a `done` signal when it's done.
            self._spotify_web_prompt = SpotifyWebPrompt(
                self.config.client_id, self.config.client_secret,
                self.config.redirect_uri)
            self._spotify_web_prompt.done.connect(self.start_spotify_web_api)
            self.layout.addWidget(self._spotify_web_prompt)

    def start_spotify_web_api(self, token: 'RefreshingToken',
                              save_config: bool = True) -> None:
        """
        SPOTIFY WEB API CUSTOM FUNCTION

        Initializes the Web API, also saving them in the config for future
        usage (if `save_config` is true).
        """
        from vidify.api.spotify.web import SpotifyWebAPI

        logging.info("Initializing the Spotify Web API")

        # Initializing the web API
        self.api = SpotifyWebAPI(token)
        api_data = APIData['SPOTIFY_WEB']
        self.wait_for_connection(
            self.api.connect_api, message=api_data.connect_msg,
            event_loop_interval=api_data.event_loop_interval)

        # The obtained credentials are saved for the future
        if save_config:
            logging.info("Saving the Spotify Web API credentials")
            self.config.client_secret = self._spotify_web_prompt.client_secret
            self.config.client_id = self._spotify_web_prompt.client_id
            self.config.refresh_token = token.refresh_token

        # The credentials prompt widget is removed after saving the data. It
        # may not exist because start_spotify_web_api was called directly,
        # so errors are taken into account.
        try:
            self.layout.removeWidget(self._spotify_web_prompt)
            self._spotify_web_prompt.hide()
            del self._spotify_web_prompt
        except AttributeError:
            pass
Beispiel #31
0
class ShellWidget(NamepaceBrowserWidget, HelpWidget, DebuggingWidget,
                  FigureBrowserWidget):
    """
    Shell widget for the IPython Console

    This is the widget in charge of executing code
    """
    # NOTE: Signals can't be assigned separately to each widget
    #       That's why we define all needed signals here.

    # For NamepaceBrowserWidget
    sig_show_syspath = Signal(object)
    sig_show_env = Signal(object)

    # For FigureBrowserWidget
    sig_new_inline_figure = Signal(object, str)

    # For DebuggingWidget
    sig_pdb_step = Signal(str, int)
    sig_pdb_state = Signal(bool, dict)

    # For ShellWidget
    focus_changed = Signal()
    new_client = Signal()
    sig_is_spykernel = Signal(object)
    sig_kernel_restarted_message = Signal(str)
    sig_kernel_restarted = Signal()
    sig_prompt_ready = Signal()
    sig_remote_execute = Signal()

    # For global working directory
    sig_change_cwd = Signal(str)

    # For printing internal errors
    sig_exception_occurred = Signal(dict)

    def __init__(self, ipyclient, additional_options, interpreter_versions,
                 external_kernel, *args, **kw):
        # To override the Qt widget used by RichJupyterWidget
        self.custom_control = ControlWidget
        self.custom_page_control = PageControlWidget
        self.custom_edit = True
        self.spyder_kernel_comm = KernelComm()
        self.spyder_kernel_comm.sig_exception_occurred.connect(
            self.sig_exception_occurred)
        super(ShellWidget, self).__init__(*args, **kw)

        self.ipyclient = ipyclient
        self.additional_options = additional_options
        self.interpreter_versions = interpreter_versions
        self.external_kernel = external_kernel
        self._cwd = ''

        # Keyboard shortcuts
        self.shortcuts = self.create_shortcuts()

        # Set the color of the matched parentheses here since the qtconsole
        # uses a hard-coded value that is not modified when the color scheme is
        # set in the qtconsole constructor. See spyder-ide/spyder#4806.
        self.set_bracket_matcher_color_scheme(self.syntax_style)

        self.shutdown_called = False
        self.kernel_manager = None
        self.kernel_client = None
        self.shutdown_thread = None
        handlers = {
            'pdb_state': self.set_pdb_state,
            'pdb_continue': self.pdb_continue,
            'get_pdb_settings': self.handle_get_pdb_settings,
            'run_cell': self.handle_run_cell,
            'cell_count': self.handle_cell_count,
            'current_filename': self.handle_current_filename,
            'get_file_code': self.handle_get_file_code,
            'set_debug_state': self.handle_debug_state,
            'update_syspath': self.update_syspath,
            'do_where': self.do_where,
        }
        for request_id in handlers:
            self.spyder_kernel_comm.register_call_handler(
                request_id, handlers[request_id])

    def __del__(self):
        """Avoid destroying shutdown_thread."""
        if (self.shutdown_thread is not None
                and self.shutdown_thread.isRunning()):
            self.shutdown_thread.wait()

    # ---- Public API ---------------------------------------------------------
    def shutdown(self):
        """Shutdown kernel"""
        self.shutdown_called = True
        self.spyder_kernel_comm.close()
        self.spyder_kernel_comm.shutdown_comm_channel()
        self.kernel_manager.stop_restarter()

        self.shutdown_thread = QThread()
        self.shutdown_thread.run = self.kernel_manager.shutdown_kernel
        if self.kernel_client is not None:
            self.shutdown_thread.finished.connect(
                self.kernel_client.stop_channels)
        self.shutdown_thread.start()

    def will_close(self, externally_managed):
        """
        Close communication channels with the kernel if shutdown was not
        called. If the kernel is not externally managed, shutdown the kernel
        as well.
        """
        if not self.shutdown_called and not externally_managed:
            # Make sure the channels are stopped
            self.spyder_kernel_comm.close()
            self.spyder_kernel_comm.shutdown_comm_channel()
            self.kernel_manager.stop_restarter()
            self.kernel_manager.shutdown_kernel(now=True)
            if self.kernel_client is not None:
                self.kernel_client.stop_channels()
        if externally_managed:
            self.spyder_kernel_comm.close()
            if self.kernel_client is not None:
                self.kernel_client.stop_channels()
        super(ShellWidget, self).will_close(externally_managed)

    def call_kernel(self, interrupt=False, blocking=False, callback=None,
                    timeout=None):
        """
        Send message to Spyder kernel connected to this console.

        Parameters
        ----------
        interrupt: bool
            Interrupt the kernel while running or in Pdb to perform
            the call.
        blocking: bool
            Make a blocking call, i.e. wait on this side until the
            kernel sends its response.
        callback: callable
            Callable to process the response sent from the kernel
            on the Spyder side.
        timeout: int or None
            Maximum time (in seconds) before giving up when making a
            blocking call to the kernel. If None, a default timeout
            (defined in commbase.py, present in spyder-kernels) is
            used.
        """
        return self.spyder_kernel_comm.remote_call(
            interrupt=interrupt,
            blocking=blocking,
            callback=callback,
            timeout=timeout
        )

    def set_kernel_client_and_manager(self, kernel_client, kernel_manager):
        """Set the kernel client and manager"""
        self.kernel_manager = kernel_manager
        self.kernel_client = kernel_client
        self.spyder_kernel_comm.open_comm(kernel_client)

        # Redefine the complete method to work while debugging.
        self.redefine_complete_for_dbg(self.kernel_client)

    def set_exit_callback(self):
        """Set exit callback for this shell."""
        self.exit_requested.connect(self.ipyclient.exit_callback)

    def is_running(self):
        if self.kernel_client is not None and \
          self.kernel_client.channels_running:
            return True
        else:
            return False

    def is_spyder_kernel(self):
        """Determine if the kernel is from Spyder."""
        code = u"getattr(get_ipython().kernel, 'set_value', False)"
        if self._reading:
            return
        else:
            self.silent_exec_method(code)

    def set_cwd(self, dirname):
        """Set shell current working directory."""
        if os.name == 'nt':
            # Use normpath instead of replacing '\' with '\\'
            # See spyder-ide/spyder#10785
            dirname = osp.normpath(dirname)

        if self.ipyclient.hostname is None:
            self.call_kernel(interrupt=True).set_cwd(dirname)
            self._cwd = dirname

    def update_cwd(self):
        """Update current working directory.

        Retrieve the cwd and emit a signal connected to the working directory
        widget. (see: handle_exec_method())
        """
        if self.kernel_client is None:
            return
        self.call_kernel(callback=self.remote_set_cwd).get_cwd()

    def remote_set_cwd(self, cwd):
        """Get current working directory from kernel."""
        self._cwd = cwd
        self.sig_change_cwd.emit(self._cwd)

    def set_bracket_matcher_color_scheme(self, color_scheme):
        """Set color scheme for matched parentheses."""
        bsh = sh.BaseSH(parent=self, color_scheme=color_scheme)
        mpcolor = bsh.get_matched_p_color()
        self._bracket_matcher.format.setBackground(mpcolor)

    def set_color_scheme(self, color_scheme, reset=True):
        """Set color scheme of the shell."""
        self.set_bracket_matcher_color_scheme(color_scheme)
        self.style_sheet, dark_color = create_qss_style(color_scheme)
        self.syntax_style = color_scheme
        self._style_sheet_changed()
        self._syntax_style_changed()
        if reset:
            self.reset(clear=True)
        if not dark_color:
            # Needed to change the colors of tracebacks
            self.silent_execute("%colors linux")
            self.call_kernel().set_sympy_forecolor(background_color='dark')
        else:
            self.silent_execute("%colors lightbg")
            self.call_kernel().set_sympy_forecolor(background_color='light')

    def update_syspath(self, path_dict, new_path_dict):
        """Update sys.path contents on kernel."""
        self.call_kernel(
            interrupt=True,
            blocking=False).update_syspath(path_dict, new_path_dict)

    def request_syspath(self):
        """Ask the kernel for sys.path contents."""
        self.call_kernel(
            interrupt=True, callback=self.sig_show_syspath.emit).get_syspath()

    def request_env(self):
        """Ask the kernel for environment variables."""
        self.call_kernel(
            interrupt=True, callback=self.sig_show_env.emit).get_env()

    # --- To handle the banner
    def long_banner(self):
        """Banner for clients with additional content."""
        # Default banner
        py_ver = self.interpreter_versions['python_version'].split('\n')[0]
        ipy_ver = self.interpreter_versions['ipython_version']

        banner_parts = [
            'Python %s\n' % py_ver,
            'Type "copyright", "credits" or "license" for more information.\n\n',
            'IPython %s -- An enhanced Interactive Python.\n' % ipy_ver
        ]
        banner = ''.join(banner_parts)

        # Pylab additions
        pylab_o = self.additional_options['pylab']
        autoload_pylab_o = self.additional_options['autoload_pylab']
        mpl_installed = programs.is_module_installed('matplotlib')
        if mpl_installed and (pylab_o and autoload_pylab_o):
            pylab_message = ("\nPopulating the interactive namespace from "
                             "numpy and matplotlib\n")
            banner = banner + pylab_message

        # Sympy additions
        sympy_o = self.additional_options['sympy']
        if sympy_o:
            lines = """
These commands were executed:
>>> from __future__ import division
>>> from sympy import *
>>> x, y, z, t = symbols('x y z t')
>>> k, m, n = symbols('k m n', integer=True)
>>> f, g, h = symbols('f g h', cls=Function)
"""
            banner = banner + lines
        if (pylab_o and sympy_o):
            lines = """
Warning: pylab (numpy and matplotlib) and symbolic math (sympy) are both
enabled at the same time. Some pylab functions are going to be overrided by
the sympy module (e.g. plot)
"""
            banner = banner + lines

        return banner

    def short_banner(self):
        """Short banner with Python and IPython versions only."""
        py_ver = self.interpreter_versions['python_version'].split(' ')[0]
        ipy_ver = self.interpreter_versions['ipython_version']
        banner = 'Python %s -- IPython %s' % (py_ver, ipy_ver)
        return banner

    # --- To define additional shortcuts
    def clear_console(self):
        if self.is_waiting_pdb_input():
            self.dbg_exec_magic('clear')
        else:
            self.execute("%clear")
        # Stop reading as any input has been removed.
        self._reading = False

    def _reset_namespace(self):
        warning = CONF.get('ipython_console', 'show_reset_namespace_warning')
        self.reset_namespace(warning=warning)

    def reset_namespace(self, warning=False, message=False):
        """Reset the namespace by removing all names defined by the user."""
        reset_str = _("Remove all variables")
        warn_str = _("All user-defined variables will be removed. "
                     "Are you sure you want to proceed?")

        # Don't show the warning when running our tests.
        if running_under_pytest():
            warning = False

        # This is necessary to make resetting variables work in external
        # kernels.
        # See spyder-ide/spyder#9505.
        try:
            kernel_env = self.kernel_manager._kernel_spec.env
        except AttributeError:
            kernel_env = {}

        if warning:
            box = MessageCheckBox(icon=QMessageBox.Warning, parent=self)
            box.setWindowTitle(reset_str)
            box.set_checkbox_text(_("Don't show again."))
            box.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
            box.setDefaultButton(QMessageBox.Yes)

            box.set_checked(False)
            box.set_check_visible(True)
            box.setText(warn_str)

            answer = box.exec_()

            # Update checkbox based on user interaction
            CONF.set('ipython_console', 'show_reset_namespace_warning',
                     not box.is_checked())
            self.ipyclient.reset_warning = not box.is_checked()

            if answer != QMessageBox.Yes:
                return

        try:
            if self.is_waiting_pdb_input():
                self.dbg_exec_magic('reset', '-f')
            else:
                if message:
                    self.reset()
                    self._append_html(_("<br><br>Removing all variables..."
                                        "\n<hr>"),
                                      before_prompt=False)
                self.silent_execute("%reset -f")
                if kernel_env.get('SPY_AUTOLOAD_PYLAB_O') == 'True':
                    self.silent_execute("from pylab import *")
                if kernel_env.get('SPY_SYMPY_O') == 'True':
                    sympy_init = """
                        from __future__ import division
                        from sympy import *
                        x, y, z, t = symbols('x y z t')
                        k, m, n = symbols('k m n', integer=True)
                        f, g, h = symbols('f g h', cls=Function)
                        init_printing()"""
                    self.silent_execute(dedent(sympy_init))
                if kernel_env.get('SPY_RUN_CYTHON') == 'True':
                    self.silent_execute("%reload_ext Cython")

                # This doesn't need to interrupt the kernel because
                # "%reset -f" is being executed before it.
                # Fixes spyder-ide/spyder#12689
                self.refresh_namespacebrowser(interrupt=False)

                if not self.external_kernel:
                    self.call_kernel().close_all_mpl_figures()
        except AttributeError:
            pass

    def create_shortcuts(self):
        """Create shortcuts for ipyconsole."""
        inspect = CONF.config_shortcut(
            self._control.inspect_current_object,
            context='Console',
            name='Inspect current object',
            parent=self)

        clear_console = CONF.config_shortcut(
            self.clear_console,
            context='Console',
            name='Clear shell',
            parent=self)

        restart_kernel = CONF.config_shortcut(
            self.ipyclient.restart_kernel,
            context='ipython_console',
            name='Restart kernel',
            parent=self)

        new_tab = CONF.config_shortcut(
            lambda: self.new_client.emit(),
            context='ipython_console',
            name='new tab',
            parent=self)

        reset_namespace = CONF.config_shortcut(
            lambda: self._reset_namespace(),
            context='ipython_console',
            name='reset namespace',
            parent=self)

        array_inline = CONF.config_shortcut(
            self._control.enter_array_inline,
            context='array_builder',
            name='enter array inline',
            parent=self)

        array_table = CONF.config_shortcut(
            self._control.enter_array_table,
            context='array_builder',
            name='enter array table',
            parent=self)

        clear_line = CONF.config_shortcut(
            self.ipyclient.clear_line,
            context='console',
            name='clear line',
            parent=self)

        return [inspect, clear_console, restart_kernel, new_tab,
                reset_namespace, array_inline, array_table, clear_line]

    # --- To communicate with the kernel
    def silent_execute(self, code):
        """Execute code in the kernel without increasing the prompt"""
        try:
            self.kernel_client.execute(to_text_string(code), silent=True)
        except AttributeError:
            pass

    def silent_exec_method(self, code):
        """Silently execute a kernel method and save its reply

        The methods passed here **don't** involve getting the value
        of a variable but instead replies that can be handled by
        ast.literal_eval.

        To get a value see `get_value`

        Parameters
        ----------
        code : string
            Code that contains the kernel method as part of its
            string

        See Also
        --------
        handle_exec_method : Method that deals with the reply

        Note
        ----
        This is based on the _silent_exec_callback method of
        RichJupyterWidget. Therefore this is licensed BSD
        """
        # Generate uuid, which would be used as an indication of whether or
        # not the unique request originated from here
        local_uuid = to_text_string(uuid.uuid1())
        code = to_text_string(code)
        if self.kernel_client is None:
            return

        msg_id = self.kernel_client.execute('', silent=True,
                                            user_expressions={ local_uuid:code })
        self._kernel_methods[local_uuid] = code
        self._request_info['execute'][msg_id] = self._ExecutionRequest(msg_id,
                                                          'silent_exec_method')

    def handle_exec_method(self, msg):
        """
        Handle data returned by silent executions of kernel methods

        This is based on the _handle_exec_callback of RichJupyterWidget.
        Therefore this is licensed BSD.
        """
        user_exp = msg['content'].get('user_expressions')
        if not user_exp:
            return
        for expression in user_exp:
            if expression in self._kernel_methods:
                # Process kernel reply
                method = self._kernel_methods[expression]
                reply = user_exp[expression]
                data = reply.get('data')
                if 'getattr' in method:
                    if data is not None and 'text/plain' in data:
                        is_spyder_kernel = data['text/plain']
                        if 'SpyderKernel' in is_spyder_kernel:
                            self.sig_is_spykernel.emit(self)

                # Remove method after being processed
                self._kernel_methods.pop(expression)

    def set_backend_for_mayavi(self, command):
        """
        Mayavi plots require the Qt backend, so we try to detect if one is
        generated to change backends
        """
        calling_mayavi = False
        lines = command.splitlines()
        for l in lines:
            if not l.startswith('#'):
                if 'import mayavi' in l or 'from mayavi' in l:
                    calling_mayavi = True
                    break
        if calling_mayavi:
            message = _("Changing backend to Qt for Mayavi")
            self._append_plain_text(message + '\n')
            self.silent_execute("%gui inline\n%gui qt")

    def change_mpl_backend(self, command):
        """
        If the user is trying to change Matplotlib backends with
        %matplotlib, send the same command again to the kernel to
        correctly change it.

        Fixes spyder-ide/spyder#4002.
        """
        if command.startswith('%matplotlib') and \
          len(command.splitlines()) == 1:
            if not 'inline' in command:
                self.silent_execute(command)

    # ---- Spyder-kernels methods ---------------------------------------------
    def get_editor(self, filename):
        """Get editor for filename and set it as the current editor."""
        editorstack = self.get_editorstack()
        if editorstack is None:
            return None

        if not filename:
            return None

        index = editorstack.has_filename(filename)
        if index is None:
            return None

        return editorstack.data[index].editor

    def get_editorstack(self):
        """Get the current editorstack."""
        plugin = self.ipyclient.plugin
        if plugin.main.editor is not None:
            editor = plugin.main.editor
            return editor.get_current_editorstack()
        raise RuntimeError('No editorstack found.')

    def handle_get_file_code(self, filename, save_all=True):
        """
        Return the bytes that compose the file.

        Bytes are returned instead of str to support non utf-8 files.
        """
        editorstack = self.get_editorstack()
        if save_all and CONF.get('editor', 'save_all_before_run', True):
            editorstack.save_all(save_new_files=False)
        editor = self.get_editor(filename)

        if editor is None:
            # Load it from file instead
            text, _enc = encoding.read(filename)
            return text

        return editor.toPlainText()

    def handle_run_cell(self, cell_name, filename):
        """
        Get cell code from cell name and file name.
        """
        editorstack = self.get_editorstack()
        editor = self.get_editor(filename)

        if editor is None:
            raise RuntimeError(
                "File {} not open in the editor".format(filename))

        editorstack.last_cell_call = (filename, cell_name)

        # The file is open, load code from editor
        return editor.get_cell_code(cell_name)

    def handle_cell_count(self, filename):
        """Get number of cells in file to loop."""
        editorstack = self.get_editorstack()
        editor = self.get_editor(filename)

        if editor is None:
            raise RuntimeError(
                "File {} not open in the editor".format(filename))

        # The file is open, get cell count from editor
        return editor.get_cell_count()

    def handle_current_filename(self):
        """Get the current filename."""
        return self.get_editorstack().get_current_finfo().filename

    # ---- Public methods (overrode by us) ------------------------------------
    def request_restart_kernel(self):
        """Reimplemented to call our own restart mechanism."""
        self.ipyclient.restart_kernel()

    # ---- Private methods (overrode by us) -----------------------------------
    def _handle_error(self, msg):
        """
        Reimplemented to reset the prompt if the error comes after the reply
        """
        self._process_execute_error(msg)

    def _context_menu_make(self, pos):
        """Reimplement the IPython context menu"""
        menu = super(ShellWidget, self)._context_menu_make(pos)
        return self.ipyclient.add_actions_to_context_menu(menu)

    def _banner_default(self):
        """
        Reimplement banner creation to let the user decide if he wants a
        banner or not
        """
        # Don't change banner for external kernels
        if self.external_kernel:
            return ''
        show_banner_o = self.additional_options['show_banner']
        if show_banner_o:
            return self.long_banner()
        else:
            return self.short_banner()

    def _kernel_restarted_message(self, died=True):
        msg = _("Kernel died, restarting") if died else _("Kernel restarting")
        self.sig_kernel_restarted_message.emit(msg)

    def _handle_kernel_restarted(self):
        super(ShellWidget, self)._handle_kernel_restarted()
        self.sig_kernel_restarted.emit()

    def _syntax_style_changed(self):
        """Refresh the highlighting with the current syntax style by class."""
        if self._highlighter is None:
            # ignore premature calls
            return
        if self.syntax_style:
            self._highlighter._style = create_style_class(self.syntax_style)
            self._highlighter._clear_caches()
        else:
            self._highlighter.set_style_sheet(self.style_sheet)

    def _prompt_started_hook(self):
        """Emit a signal when the prompt is ready."""
        if not self._reading:
            self._highlighter.highlighting_on = True
            self.sig_prompt_ready.emit()

    def _handle_execute_input(self, msg):
        """Handle an execute_input message"""
        super(ShellWidget, self)._handle_execute_input(msg)
        self.sig_remote_execute.emit()

    #---- Qt methods ----------------------------------------------------------
    def focusInEvent(self, event):
        """Reimplement Qt method to send focus change notification"""
        self.focus_changed.emit()
        return super(ShellWidget, self).focusInEvent(event)

    def focusOutEvent(self, event):
        """Reimplement Qt method to send focus change notification"""
        self.focus_changed.emit()
        return super(ShellWidget, self).focusOutEvent(event)
Beispiel #32
0
class ClientWidget(QWidget, SaveHistoryMixin, SpyderWidgetMixin):
    """
    Client widget for the IPython Console

    This widget is necessary to handle the interaction between the
    plugin and each shell widget.
    """

    sig_append_to_history_requested = Signal(str, str)
    sig_execution_state_changed = Signal()

    CONF_SECTION = 'ipython_console'
    SEPARATOR = '{0}## ---({1})---'.format(os.linesep*2, time.ctime())
    INITHISTORY = ['# -*- coding: utf-8 -*-',
                   '# *** Spyder Python Console History Log ***', ]

    def __init__(self, parent, id_,
                 history_filename, config_options,
                 additional_options, interpreter_versions,
                 connection_file=None, hostname=None,
                 context_menu_actions=(),
                 menu_actions=None,
                 is_external_kernel=False,
                 is_spyder_kernel=True,
                 given_name=None,
                 give_focus=True,
                 options_button=None,
                 time_label=None,
                 show_elapsed_time=False,
                 reset_warning=True,
                 ask_before_restart=True,
                 ask_before_closing=False,
                 css_path=None,
                 handlers={},
                 stderr_obj=None,
                 stdout_obj=None,
                 fault_obj=None):
        super(ClientWidget, self).__init__(parent)
        SaveHistoryMixin.__init__(self, history_filename)

        # --- Init attrs
        self.container = parent
        self.id_ = id_
        self.connection_file = connection_file
        self.hostname = hostname
        self.menu_actions = menu_actions
        self.is_external_kernel = is_external_kernel
        self.given_name = given_name
        self.show_elapsed_time = show_elapsed_time
        self.reset_warning = reset_warning
        self.ask_before_restart = ask_before_restart
        self.ask_before_closing = ask_before_closing

        # --- Other attrs
        self.context_menu_actions = context_menu_actions
        self.time_label = time_label
        self.options_button = options_button
        self.history = []
        self.allow_rename = True
        self.is_error_shown = False
        self.error_text = None
        self.restart_thread = None
        self.give_focus = give_focus

        if css_path is None:
            self.css_path = CSS_PATH
        else:
            self.css_path = css_path

        # --- Widgets
        self.shellwidget = ShellWidget(
            config=config_options,
            ipyclient=self,
            additional_options=additional_options,
            interpreter_versions=interpreter_versions,
            is_external_kernel=is_external_kernel,
            is_spyder_kernel=is_spyder_kernel,
            handlers=handlers,
            local_kernel=True
        )
        self.infowidget = self.container.infowidget
        self.blank_page = self._create_blank_page()
        self.loading_page = self._create_loading_page()
        # To keep a reference to the page to be displayed
        # in infowidget
        self.info_page = None
        self._before_prompt_is_ready()

        # Elapsed time
        self.t0 = time.monotonic()
        self.timer = QTimer(self)

        # --- Layout
        self.layout = QVBoxLayout()
        self.layout.setContentsMargins(0, 0, 0, 0)
        self.layout.addWidget(self.shellwidget)
        if self.infowidget is not None:
            self.layout.addWidget(self.infowidget)
        self.setLayout(self.layout)

        # --- Exit function
        self.exit_callback = lambda: self.container.close_client(client=self)

        # --- Dialog manager
        self.dialog_manager = DialogManager()

        # --- Standard files handling
        self.stderr_obj = stderr_obj
        self.stdout_obj = stdout_obj
        self.fault_obj = fault_obj
        self.std_poll_timer = None
        if self.stderr_obj is not None or self.stdout_obj is not None:
            self.std_poll_timer = QTimer(self)
            self.std_poll_timer.timeout.connect(self.poll_std_file_change)
            self.std_poll_timer.setInterval(1000)
            self.std_poll_timer.start()
            self.shellwidget.executed.connect(self.poll_std_file_change)

        self.start_successful = False

    def __del__(self):
        """Close threads to avoid segfault."""
        if (self.restart_thread is not None
                and self.restart_thread.isRunning()):
            self.restart_thread.quit()
            self.restart_thread.wait()

    # ----- Private methods ---------------------------------------------------
    def _before_prompt_is_ready(self, show_loading_page=True):
        """Configuration before kernel is connected."""
        if show_loading_page:
            self._show_loading_page()
        self.shellwidget.sig_prompt_ready.connect(
            self._when_prompt_is_ready)
        # If remote execution, the loading page should be hidden as well
        self.shellwidget.sig_remote_execute.connect(
            self._when_prompt_is_ready)

    def _when_prompt_is_ready(self):
        """Configuration after the prompt is shown."""
        self.start_successful = True
        # To hide the loading page
        self._hide_loading_page()

        # Show possible errors when setting Matplotlib backend
        self._show_mpl_backend_errors()

        # To show if special console is valid
        self._check_special_console_error()

        # Set the initial current working directory
        self._set_initial_cwd()

        self.shellwidget.sig_prompt_ready.disconnect(
            self._when_prompt_is_ready)
        self.shellwidget.sig_remote_execute.disconnect(
            self._when_prompt_is_ready)

        # It's necessary to do this at this point to avoid giving
        # focus to _control at startup.
        self._connect_control_signals()

        if self.give_focus:
            self.shellwidget._control.setFocus()

    def _create_loading_page(self):
        """Create html page to show while the kernel is starting"""
        loading_template = Template(LOADING)
        loading_img = get_image_path('loading_sprites')
        if os.name == 'nt':
            loading_img = loading_img.replace('\\', '/')
        message = _("Connecting to kernel...")
        page = loading_template.substitute(css_path=self.css_path,
                                           loading_img=loading_img,
                                           message=message)
        return page

    def _create_blank_page(self):
        """Create html page to show while the kernel is starting"""
        loading_template = Template(BLANK)
        page = loading_template.substitute(css_path=self.css_path)
        return page

    def _show_loading_page(self):
        """Show animation while the kernel is loading."""
        if self.infowidget is not None:
            self.shellwidget.hide()
            self.infowidget.show()
            self.info_page = self.loading_page
            self.set_info_page()

    def _hide_loading_page(self):
        """Hide animation shown while the kernel is loading."""
        if self.infowidget is not None:
            self.infowidget.hide()
            self.info_page = self.blank_page
            self.set_info_page()
        self.shellwidget.show()

    def _read_stderr(self):
        """Read the stderr file of the kernel."""
        # We need to read stderr_file as bytes to be able to
        # detect its encoding with chardet
        f = open(self.stderr_file, 'rb')

        try:
            stderr_text = f.read()

            # This is needed to avoid showing an empty error message
            # when the kernel takes too much time to start.
            # See spyder-ide/spyder#8581.
            if not stderr_text:
                return ''

            # This is needed since the stderr file could be encoded
            # in something different to utf-8.
            # See spyder-ide/spyder#4191.
            encoding = get_coding(stderr_text)
            stderr_text = to_text_string(stderr_text, encoding)
            return stderr_text
        finally:
            f.close()

    def _show_mpl_backend_errors(self):
        """
        Show possible errors when setting the selected Matplotlib backend.
        """
        if self.shellwidget.is_spyder_kernel:
            self.shellwidget.call_kernel().show_mpl_backend_errors()

    def _check_special_console_error(self):
        """Check if the dependecies for special consoles are available."""
        self.shellwidget.call_kernel(
            callback=self._show_special_console_error
            ).is_special_kernel_valid()

    def _show_special_console_error(self, missing_dependency):
        if missing_dependency is not None:
            error_message = _(
                "Your Python environment or installation doesn't have the "
                "<tt>{missing_dependency}</tt> module installed or it "
                "occurred a problem importing it. Due to that, it is not "
                "possible for Spyder to create this special console for "
                "you."
            ).format(missing_dependency=missing_dependency)

            self.show_kernel_error(error_message)

    def _abort_kernel_restart(self):
        """
        Abort kernel restart if there are errors while starting it.

        We also ignore errors about comms, which are irrelevant.
        """
        if self.start_successful:
            return False
        stderr = self.stderr_obj.get_contents()
        if not stderr:
            return False
        # There is an error. If it is benign, ignore.
        for line in stderr.splitlines():
            if line and not self.is_benign_error(line):
                return True
        return False

    def _connect_control_signals(self):
        """Connect signals of control widgets."""
        control = self.shellwidget._control
        page_control = self.shellwidget._page_control

        control.sig_focus_changed.connect(
            self.container.sig_focus_changed)
        page_control.sig_focus_changed.connect(
            self.container.sig_focus_changed)
        control.sig_visibility_changed.connect(
            self.container.refresh_container)
        page_control.sig_visibility_changed.connect(
            self.container.refresh_container)
        page_control.sig_show_find_widget_requested.connect(
            self.container.find_widget.show)

    def _set_initial_cwd(self):
        """Set initial cwd according to preferences."""
        logger.debug("Setting initial working directory")
        cwd_path = get_home_dir()
        project_path = self.container.get_active_project_path()

        # This is for the first client
        if self.id_['int_id'] == '1':
            if self.get_conf(
                'startup/use_project_or_home_directory',
                section='workingdir'
            ):
                cwd_path = get_home_dir()
                if project_path is not None:
                    cwd_path = project_path
            elif self.get_conf(
                'startup/use_fixed_directory',
                section='workingdir'
            ):
                cwd_path = self.get_conf(
                    'startup/fixed_directory',
                    default=get_home_dir(),
                    section='workingdir'
                )
        else:
            # For new clients
            if self.get_conf(
                'console/use_project_or_home_directory',
                section='workingdir'
            ):
                cwd_path = get_home_dir()
                if project_path is not None:
                    cwd_path = project_path
            elif self.get_conf('console/use_cwd', section='workingdir'):
                cwd_path = self.container.get_working_directory()
            elif self.get_conf(
                'console/use_fixed_directory',
                section='workingdir'
            ):
                cwd_path = self.get_conf(
                    'console/fixed_directory',
                    default=get_home_dir(),
                    section='workingdir'
                )

        if osp.isdir(cwd_path):
            self.shellwidget.set_cwd(cwd_path)

    # ----- Public API --------------------------------------------------------
    @property
    def kernel_id(self):
        """Get kernel id."""
        if self.connection_file is not None:
            json_file = osp.basename(self.connection_file)
            return json_file.split('.json')[0]

    def remove_std_files(self, is_last_client=True):
        """Remove stderr_file associated with the client."""
        try:
            self.shellwidget.executed.disconnect(self.poll_std_file_change)
        except TypeError:
            pass
        if self.std_poll_timer is not None:
            self.std_poll_timer.stop()
        if is_last_client:
            if self.stderr_obj is not None:
                self.stderr_obj.remove()
            if self.stdout_obj is not None:
                self.stdout_obj.remove()
            if self.fault_obj is not None:
                self.fault_obj.remove()

    @Slot()
    def poll_std_file_change(self):
        """Check if the stderr or stdout file just changed."""
        self.shellwidget.call_kernel().flush_std()
        starting = self.shellwidget._starting
        if self.stderr_obj is not None:
            stderr = self.stderr_obj.poll_file_change()
            if stderr:
                if self.is_benign_error(stderr):
                    return
                if self.shellwidget.isHidden():
                    # Avoid printing the same thing again
                    if self.error_text != '<tt>%s</tt>' % stderr:
                        full_stderr = self.stderr_obj.get_contents()
                        self.show_kernel_error('<tt>%s</tt>' % full_stderr)
                if starting:
                    self.shellwidget.banner = (
                        stderr + '\n' + self.shellwidget.banner)
                else:
                    self.shellwidget._append_plain_text(
                        '\n' + stderr, before_prompt=True)

        if self.stdout_obj is not None:
            stdout = self.stdout_obj.poll_file_change()
            if stdout:
                if starting:
                    self.shellwidget.banner = (
                        stdout + '\n' + self.shellwidget.banner)
                else:
                    self.shellwidget._append_plain_text(
                        '\n' + stdout, before_prompt=True)

    def configure_shellwidget(self, give_focus=True):
        """Configure shellwidget after kernel is connected."""
        self.give_focus = give_focus

        # Make sure the kernel sends the comm config over
        self.shellwidget.call_kernel()._send_comm_config()

        # Set exit callback
        self.shellwidget.set_exit_callback()

        # To save history
        self.shellwidget.executing.connect(self.add_to_history)

        # For Mayavi to run correctly
        self.shellwidget.executing.connect(
            self.shellwidget.set_backend_for_mayavi)

        # To update history after execution
        self.shellwidget.executed.connect(self.update_history)

        # To update the Variable Explorer after execution
        self.shellwidget.executed.connect(
            self.shellwidget.refresh_namespacebrowser)

        # To enable the stop button when executing a process
        self.shellwidget.executing.connect(
            self.sig_execution_state_changed)

        # To disable the stop button after execution stopped
        self.shellwidget.executed.connect(
            self.sig_execution_state_changed)

        # To show kernel restarted/died messages
        self.shellwidget.sig_kernel_restarted_message.connect(
            self.kernel_restarted_message)
        self.shellwidget.sig_kernel_restarted.connect(
            self._finalise_restart)

        # To correctly change Matplotlib backend interactively
        self.shellwidget.executing.connect(
            self.shellwidget.change_mpl_backend)

        # To show env and sys.path contents
        self.shellwidget.sig_show_syspath.connect(self.show_syspath)
        self.shellwidget.sig_show_env.connect(self.show_env)

        # To sync with working directory toolbar
        self.shellwidget.executed.connect(self.shellwidget.update_cwd)

        # To apply style
        self.set_color_scheme(self.shellwidget.syntax_style, reset=False)

        if self.fault_obj is not None:
            # To display faulthandler
            self.shellwidget.call_kernel().enable_faulthandler(
                self.fault_obj.filename)

    def add_to_history(self, command):
        """Add command to history"""
        if self.shellwidget.is_debugging():
            return
        return super(ClientWidget, self).add_to_history(command)

    def is_client_executing(self):
        return (self.shellwidget._executing or
                self.shellwidget.is_waiting_pdb_input())

    @Slot()
    def stop_button_click_handler(self):
        """Method to handle what to do when the stop button is pressed"""
        # Interrupt computations or stop debugging
        if not self.shellwidget.is_waiting_pdb_input():
            self.interrupt_kernel()
        else:
            self.shellwidget.pdb_execute_command('exit')

    def show_kernel_error(self, error):
        """Show kernel initialization errors in infowidget."""
        self.error_text = error

        if self.is_benign_error(error):
            return

        InstallerIPythonKernelError(error)

        # Replace end of line chars with <br>
        eol = sourcecode.get_eol_chars(error)
        if eol:
            error = error.replace(eol, '<br>')

        # Don't break lines in hyphens
        # From https://stackoverflow.com/q/7691569/438386
        error = error.replace('-', '&#8209')

        # Create error page
        message = _("An error ocurred while starting the kernel")
        kernel_error_template = Template(KERNEL_ERROR)
        self.info_page = kernel_error_template.substitute(
            css_path=self.css_path,
            message=message,
            error=error)

        # Show error
        if self.infowidget is not None:
            self.set_info_page()
            self.shellwidget.hide()
            self.infowidget.show()

        # Tell the client we're in error mode
        self.is_error_shown = True

        # Stop shellwidget
        self.shellwidget.shutdown()
        self.remove_std_files(is_last_client=False)

    def is_benign_error(self, error):
        """Decide if an error is benign in order to filter it."""
        benign_errors = [
            # Error when switching from the Qt5 backend to the Tk one.
            # See spyder-ide/spyder#17488
            "KeyboardInterrupt caught in kernel",
            "QSocketNotifier: Multiple socket notifiers for same socket",
            # Error when switching from the Tk backend to the Qt5 one.
            # See spyder-ide/spyder#17488
            "Tcl_AsyncDelete async handler deleted by the wrong thread",
            "error in background error handler:",
            "    while executing",
            '"::tcl::Bgerror',
            # Avoid showing this warning because it was up to the user to
            # disable secure writes.
            "WARNING: Insecure writes have been enabled via environment",
            # Old error
            "No such comm"
        ]

        return any([err in error for err in benign_errors])

    def get_name(self):
        """Return client name"""
        if self.given_name is None:
            # Name according to host
            if self.hostname is None:
                name = _("Console")
            else:
                name = self.hostname
            # Adding id to name
            client_id = self.id_['int_id'] + u'/' + self.id_['str_id']
            name = name + u' ' + client_id
        elif self.given_name in ["Pylab", "SymPy", "Cython"]:
            client_id = self.id_['int_id'] + u'/' + self.id_['str_id']
            name = self.given_name + u' ' + client_id
        else:
            name = self.given_name + u'/' + self.id_['str_id']
        return name

    def get_control(self):
        """Return the text widget (or similar) to give focus to"""
        # page_control is the widget used for paging
        page_control = self.shellwidget._page_control
        if page_control and page_control.isVisible():
            return page_control
        else:
            return self.shellwidget._control

    def get_kernel(self):
        """Get kernel associated with this client"""
        return self.shellwidget.kernel_manager

    def add_actions_to_context_menu(self, menu):
        """Add actions to IPython widget context menu"""
        add_actions(menu, self.context_menu_actions)

        return menu

    def set_font(self, font):
        """Set IPython widget's font"""
        self.shellwidget._control.setFont(font)
        self.shellwidget.font = font

    def set_color_scheme(self, color_scheme, reset=True):
        """Set IPython color scheme."""
        # Needed to handle not initialized kernel_client
        # See spyder-ide/spyder#6996.
        try:
            self.shellwidget.set_color_scheme(color_scheme, reset)
        except AttributeError:
            pass

    def shutdown(self, is_last_client):
        """Shutdown connection and kernel if needed."""
        self.dialog_manager.close_all()
        if (self.restart_thread is not None
                and self.restart_thread.isRunning()):
            self.restart_thread.finished.disconnect()
            self.restart_thread.quit()
            self.restart_thread.wait()
        shutdown_kernel = (
            is_last_client and not self.is_external_kernel
            and not self.is_error_shown)
        self.shellwidget.shutdown(shutdown_kernel)
        self.remove_std_files(shutdown_kernel)

    def interrupt_kernel(self):
        """Interrupt the associanted Spyder kernel if it's running"""
        # Needed to prevent a crash when a kernel is not running.
        # See spyder-ide/spyder#6299.
        try:
            self.shellwidget.request_interrupt_kernel()
        except RuntimeError:
            pass

    @Slot()
    def restart_kernel(self):
        """
        Restart the associated kernel.

        Took this code from the qtconsole project
        Licensed under the BSD license
        """
        sw = self.shellwidget

        if not running_under_pytest() and self.ask_before_restart:
            message = _('Are you sure you want to restart the kernel?')
            buttons = QMessageBox.Yes | QMessageBox.No
            result = QMessageBox.question(self, _('Restart kernel?'),
                                          message, buttons)
        else:
            result = None

        if (result == QMessageBox.Yes or
                running_under_pytest() or
                not self.ask_before_restart):
            if sw.kernel_manager:
                if self.infowidget is not None:
                    if self.infowidget.isVisible():
                        self.infowidget.hide()

                if self._abort_kernel_restart():
                    sw.spyder_kernel_comm.close()
                    return

                self._show_loading_page()

                # Close comm
                sw.spyder_kernel_comm.close()

                # Stop autorestart mechanism
                sw.kernel_manager.stop_restarter()
                sw.kernel_manager.autorestart = False

                # Reconfigure client before the new kernel is connected again.
                self._before_prompt_is_ready(show_loading_page=False)

                # Create and run restarting thread
                if (self.restart_thread is not None
                        and self.restart_thread.isRunning()):
                    self.restart_thread.finished.disconnect()
                    self.restart_thread.quit()
                    self.restart_thread.wait()
                self.restart_thread = QThread(None)
                self.restart_thread.run = self._restart_thread_main
                self.restart_thread.error = None
                self.restart_thread.finished.connect(
                    lambda: self._finalise_restart(True))
                self.restart_thread.start()

            else:
                sw._append_plain_text(
                    _('Cannot restart a kernel not started by Spyder\n'),
                    before_prompt=True
                )
                self._hide_loading_page()

    def _restart_thread_main(self):
        """Restart the kernel in a thread."""
        try:
            self.shellwidget.kernel_manager.restart_kernel(
                stderr=self.stderr_obj.handle,
                stdout=self.stdout_obj.handle)
        except RuntimeError as e:
            self.restart_thread.error = e

    def _finalise_restart(self, reset=False):
        """Finishes the restarting of the kernel."""
        sw = self.shellwidget

        if self._abort_kernel_restart():
            sw.spyder_kernel_comm.close()
            return

        if self.restart_thread and self.restart_thread.error is not None:
            sw._append_plain_text(
                _('Error restarting kernel: %s\n') % self.restart_thread.error,
                before_prompt=True
            )
        else:
            if self.fault_obj is not None:
                fault = self.fault_obj.get_contents()
                if fault:
                    fault = self.filter_fault(fault)
                    self.shellwidget._append_plain_text(
                        '\n' + fault, before_prompt=True)

            # Reset Pdb state and reopen comm
            sw._pdb_in_loop = False
            sw.spyder_kernel_comm.remove()
            try:
                sw.spyder_kernel_comm.open_comm(sw.kernel_client)
            except AttributeError:
                # An error occurred while opening our comm channel.
                # Aborting!
                return

            # Start autorestart mechanism
            sw.kernel_manager.autorestart = True
            sw.kernel_manager.start_restarter()

            # For spyder-ide/spyder#6235, IPython was changing the
            # setting of %colors on windows by assuming it was using a
            # dark background. This corrects it based on the scheme.
            self.set_color_scheme(sw.syntax_style, reset=reset)
            sw._append_html(_("<br>Restarting kernel...<br>"),
                            before_prompt=True)
            sw.insert_horizontal_ruler()
            if self.fault_obj is not None:
                self.shellwidget.call_kernel().enable_faulthandler(
                    self.fault_obj.filename)

        self._hide_loading_page()
        self.restart_thread = None
        self.sig_execution_state_changed.emit()

    def filter_fault(self, fault):
        """Get a fault from a previous session."""
        thread_regex = (
            r"(Current thread|Thread) "
            r"(0x[\da-f]+) \(most recent call first\):"
            r"(?:.|\r\n|\r|\n)+?(?=Current thread|Thread|\Z)")
        # Keep line for future improvments
        # files_regex = r"File \"([^\"]+)\", line (\d+) in (\S+)"

        main_re = "Main thread id:(?:\r\n|\r|\n)(0x[0-9a-f]+)"
        main_id = 0
        for match in re.finditer(main_re, fault):
            main_id = int(match.group(1), base=16)

        system_re = ("System threads ids:"
                     "(?:\r\n|\r|\n)(0x[0-9a-f]+(?: 0x[0-9a-f]+)+)")
        ignore_ids = []
        start_idx = 0
        for match in re.finditer(system_re, fault):
            ignore_ids = [int(i, base=16) for i in match.group(1).split()]
            start_idx = match.span()[1]
        text = ""
        for idx, match in enumerate(re.finditer(thread_regex, fault)):
            if idx == 0:
                text += fault[start_idx:match.span()[0]]
            thread_id = int(match.group(2), base=16)
            if thread_id != main_id:
                if thread_id in ignore_ids:
                    continue
                if "wurlitzer.py" in match.group(0):
                    # Wurlitzer threads are launched later
                    continue
                text += "\n" + match.group(0) + "\n"
            else:
                try:
                    pattern = (r".*(?:/IPython/core/interactiveshell\.py|"
                               r"\\IPython\\core\\interactiveshell\.py).*")
                    match_internal = next(re.finditer(pattern, match.group(0)))
                    end_idx = match_internal.span()[0]
                except StopIteration:
                    end_idx = None
                text += "\nMain thread:\n" + match.group(0)[:end_idx] + "\n"
        return text

    @Slot(str)
    def kernel_restarted_message(self, msg):
        """Show kernel restarted/died messages."""
        if self.stderr_obj is not None:
            # If there are kernel creation errors, jupyter_client will
            # try to restart the kernel and qtconsole prints a
            # message about it.
            # So we read the kernel's stderr_file and display its
            # contents in the client instead of the usual message shown
            # by qtconsole.
            self.poll_std_file_change()
        else:
            self.shellwidget._append_html("<br>%s<hr><br>" % msg,
                                          before_prompt=False)

    @Slot()
    def enter_array_inline(self):
        """Enter and show the array builder on inline mode."""
        self.shellwidget._control.enter_array_inline()

    @Slot()
    def enter_array_table(self):
        """Enter and show the array builder on table."""
        self.shellwidget._control.enter_array_table()

    @Slot()
    def inspect_object(self):
        """Show how to inspect an object with our Help plugin"""
        self.shellwidget._control.inspect_current_object()

    @Slot()
    def clear_line(self):
        """Clear a console line"""
        self.shellwidget._keyboard_quit()

    @Slot()
    def clear_console(self):
        """Clear the whole console"""
        self.shellwidget.clear_console()

    @Slot()
    def reset_namespace(self):
        """Resets the namespace by removing all names defined by the user"""
        self.shellwidget.reset_namespace(warning=self.reset_warning,
                                         message=True)

    def update_history(self):
        self.history = self.shellwidget._history

    @Slot(object)
    def show_syspath(self, syspath):
        """Show sys.path contents."""
        if syspath is not None:
            editor = CollectionsEditor(self)
            editor.setup(syspath, title="sys.path contents", readonly=True,
                         icon=ima.icon('syspath'))
            self.dialog_manager.show(editor)
        else:
            return

    @Slot(object)
    def show_env(self, env):
        """Show environment variables."""
        self.dialog_manager.show(RemoteEnvDialog(env, parent=self))

    def show_time(self, end=False):
        """Text to show in time_label."""
        if self.time_label is None:
            return

        elapsed_time = time.monotonic() - self.t0
        # System time changed to past date, so reset start.
        if elapsed_time < 0:
            self.t0 = time.monotonic()
            elapsed_time = 0
        if elapsed_time > 24 * 3600:  # More than a day...!
            fmt = "%d %H:%M:%S"
        else:
            fmt = "%H:%M:%S"
        if end:
            color = QStylePalette.COLOR_TEXT_3
        else:
            color = QStylePalette.COLOR_ACCENT_4
        text = "<span style=\'color: %s\'><b>%s" \
               "</b></span>" % (color,
                                time.strftime(fmt, time.gmtime(elapsed_time)))
        if self.show_elapsed_time:
            self.time_label.setText(text)
        else:
            self.time_label.setText("")

    @Slot(bool)
    def set_show_elapsed_time(self, state):
        """Slot to show/hide elapsed time label."""
        self.show_elapsed_time = state

    def set_info_page(self):
        """Set current info_page."""
        if self.infowidget is not None and self.info_page is not None:
            self.infowidget.setHtml(
                self.info_page,
                QUrl.fromLocalFile(self.css_path)
            )
Beispiel #33
0
class FallbackActor(QObject):
    #: Signal emitted when the Thread is ready
    sig_fallback_ready = Signal()
    sig_set_tokens = Signal(int, dict)
    sig_mailbox = Signal(dict)

    def __init__(self, parent):
        QObject.__init__(self)
        self.stopped = False
        self.daemon = True
        self.mutex = QMutex()
        self.file_tokens = {}
        self.diff_patch = diff_match_patch()
        self.thread = QThread()
        self.moveToThread(self.thread)

        self.thread.started.connect(self.started)
        self.sig_mailbox.connect(self.handle_msg)

    def tokenize(self, text, language):
        """
        Return all tokens in `text` and all keywords associated by
        Pygments to `language`.
        """
        try:
            lexer = get_lexer_by_name(language)
            keywords = get_keywords(lexer)
        except Exception:
            keywords = []
        keyword_set = set(keywords)
        keywords = [{
            'kind': CompletionItemKind.KEYWORD,
            'insertText': keyword,
            'sortText': u'zz{0}'.format(keyword[0].lower()),
            'filterText': keyword,
            'documentation': ''
        } for keyword in keywords]
        # logger.debug(keywords)
        # tokens = list(lexer.get_tokens(text))
        # logger.debug(tokens)
        tokens = get_words(text, language)
        tokens = [{
            'kind': CompletionItemKind.TEXT,
            'insertText': token,
            'sortText': u'zz{0}'.format(token[0].lower()),
            'filterText': token,
            'documentation': ''
        } for token in tokens]
        for token in tokens:
            if token['insertText'] not in keyword_set:
                keywords.append(token)
        return keywords

    def stop(self):
        """Stop actor."""
        with QMutexLocker(self.mutex):
            logger.debug("Fallback plugin stopping...")
            self.thread.quit()

    def start(self):
        """Start thread."""
        self.thread.start()

    def started(self):
        """Thread started."""
        logger.debug('Fallback plugin starting...')
        self.sig_fallback_ready.emit()

    @Slot(dict)
    def handle_msg(self, message):
        """Handle one message"""
        msg_type, _id, file, msg = [
            message[k] for k in ('type', 'id', 'file', 'msg')
        ]
        logger.debug('Got request id {0}: {1} for file {2}'.format(
            _id, msg_type, file))
        if msg_type == LSPRequestTypes.DOCUMENT_DID_OPEN:
            self.file_tokens[file] = {
                'text': msg['text'],
                'language': msg['language']
            }
        elif msg_type == LSPRequestTypes.DOCUMENT_DID_CHANGE:
            if file not in self.file_tokens:
                self.file_tokens[file] = {
                    'text': '',
                    'language': msg['language']
                }
            diff = msg['diff']
            text = self.file_tokens[file]
            text, _ = self.diff_patch.patch_apply(diff, text['text'])
            self.file_tokens[file]['text'] = text
        elif msg_type == LSPRequestTypes.DOCUMENT_DID_CLOSE:
            self.file_tokens.pop(file, {})
        elif msg_type == LSPRequestTypes.DOCUMENT_COMPLETION:
            tokens = []
            if file in self.file_tokens:
                text_info = self.file_tokens[file]
                tokens = self.tokenize(text_info['text'],
                                       text_info['language'])
            tokens = {'params': tokens}
            self.sig_set_tokens.emit(_id, tokens)
Beispiel #34
0
class ClientWidget(QWidget, SaveHistoryMixin):
    """
    Client widget for the IPython Console

    This widget is necessary to handle the interaction between the
    plugin and each shell widget.
    """

    SEPARATOR = '{0}## ---({1})---'.format(os.linesep*2, time.ctime())
    INITHISTORY = ['# -*- coding: utf-8 -*-',
                   '# *** Spyder Python Console History Log ***',]

    append_to_history = Signal(str, str)

    def __init__(self, plugin, id_,
                 history_filename, config_options,
                 additional_options, interpreter_versions,
                 connection_file=None, hostname=None,
                 menu_actions=None, slave=False,
                 external_kernel=False, given_name=None,
                 options_button=None,
                 show_elapsed_time=False,
                 reset_warning=True,
                 ask_before_restart=True,
                 css_path=None):
        super(ClientWidget, self).__init__(plugin)
        SaveHistoryMixin.__init__(self, history_filename)

        # --- Init attrs
        self.plugin = plugin
        self.id_ = id_
        self.connection_file = connection_file
        self.hostname = hostname
        self.menu_actions = menu_actions
        self.slave = slave
        self.external_kernel = external_kernel
        self.given_name = given_name
        self.show_elapsed_time = show_elapsed_time
        self.reset_warning = reset_warning
        self.ask_before_restart = ask_before_restart

        # --- Other attrs
        self.options_button = options_button
        self.stop_button = None
        self.reset_button = None
        self.stop_icon = ima.icon('stop')
        self.history = []
        self.allow_rename = True
        self.stderr_dir = None
        self.is_error_shown = False

        if css_path is None:
            self.css_path = CSS_PATH
        else:
            self.css_path = css_path

        # --- Widgets
        self.shellwidget = ShellWidget(config=config_options,
                                       ipyclient=self,
                                       additional_options=additional_options,
                                       interpreter_versions=interpreter_versions,
                                       external_kernel=external_kernel,
                                       local_kernel=True)

        self.infowidget = plugin.infowidget
        self.blank_page = self._create_blank_page()
        self.loading_page = self._create_loading_page()
        # To keep a reference to the page to be displayed
        # in infowidget
        self.info_page = None
        self._show_loading_page()

        # Elapsed time
        self.time_label = None
        self.t0 = time.monotonic()
        self.timer = QTimer(self)
        self.show_time_action = create_action(self, _("Show elapsed time"),
                                         toggled=self.set_elapsed_time_visible)

        # --- Layout
        self.layout = QVBoxLayout()
        toolbar_buttons = self.get_toolbar_buttons()

        hlayout = QHBoxLayout()
        hlayout.addWidget(self.create_time_label())
        hlayout.addStretch(0)
        for button in toolbar_buttons:
            hlayout.addWidget(button)

        self.layout.addLayout(hlayout)
        self.layout.setContentsMargins(0, 0, 0, 0)
        self.layout.addWidget(self.shellwidget)
        self.layout.addWidget(self.infowidget)
        self.setLayout(self.layout)

        # --- Exit function
        self.exit_callback = lambda: plugin.close_client(client=self)

        # --- Dialog manager
        self.dialog_manager = DialogManager()

        # Show timer
        self.update_time_label_visibility()

    #------ Public API --------------------------------------------------------
    @property
    def kernel_id(self):
        """Get kernel id"""
        if self.connection_file is not None:
            json_file = osp.basename(self.connection_file)
            return json_file.split('.json')[0]

    @property
    def stderr_file(self):
        """Filename to save kernel stderr output."""
        stderr_file = None
        if self.connection_file is not None:
            stderr_file = self.kernel_id + '.stderr'
            if self.stderr_dir is not None:
                stderr_file = osp.join(self.stderr_dir, stderr_file)
            else:
                try:
                    stderr_file = osp.join(get_temp_dir(), stderr_file)
                except (IOError, OSError):
                    stderr_file = None
        return stderr_file

    @property
    def stderr_handle(self):
        """Get handle to stderr_file."""
        if self.stderr_file is not None:
            # Needed to prevent any error that could appear.
            # See spyder-ide/spyder#6267.
            try:
                handle = codecs.open(self.stderr_file, 'w', encoding='utf-8')
            except Exception:
                handle = None
        else:
            handle = None

        return handle

    def remove_stderr_file(self):
        """Remove stderr_file associated with the client."""
        try:
            # Defer closing the stderr_handle until the client
            # is closed because jupyter_client needs it open
            # while it tries to restart the kernel
            self.stderr_handle.close()
            os.remove(self.stderr_file)
        except Exception:
            pass

    def configure_shellwidget(self, give_focus=True):
        """Configure shellwidget after kernel is started"""
        if give_focus:
            self.get_control().setFocus()

        # Set exit callback
        self.shellwidget.set_exit_callback()

        # To save history
        self.shellwidget.executing.connect(self.add_to_history)

        # For Mayavi to run correctly
        self.shellwidget.executing.connect(
            self.shellwidget.set_backend_for_mayavi)

        # To update history after execution
        self.shellwidget.executed.connect(self.update_history)

        # To update the Variable Explorer after execution
        self.shellwidget.executed.connect(
            self.shellwidget.refresh_namespacebrowser)

        # To enable the stop button when executing a process
        self.shellwidget.executing.connect(self.enable_stop_button)

        # To disable the stop button after execution stopped
        self.shellwidget.executed.connect(self.disable_stop_button)

        # To show kernel restarted/died messages
        self.shellwidget.sig_kernel_restarted.connect(
            self.kernel_restarted_message)

        # To correctly change Matplotlib backend interactively
        self.shellwidget.executing.connect(
            self.shellwidget.change_mpl_backend)

        # To show env and sys.path contents
        self.shellwidget.sig_show_syspath.connect(self.show_syspath)
        self.shellwidget.sig_show_env.connect(self.show_env)

        # To sync with working directory toolbar
        self.shellwidget.executed.connect(self.shellwidget.update_cwd)

        # To apply style
        self.set_color_scheme(self.shellwidget.syntax_style, reset=False)

        # To hide the loading page
        self.shellwidget.sig_prompt_ready.connect(self._hide_loading_page)

        # Show possible errors when setting Matplotlib backend
        self.shellwidget.sig_prompt_ready.connect(
            self._show_mpl_backend_errors)

    def enable_stop_button(self):
        self.stop_button.setEnabled(True)

    def disable_stop_button(self):
        # This avoids disabling automatically the button when
        # re-running files on dedicated consoles.
        # See spyder-ide/spyder#5958.
        if not self.shellwidget._executing:
            self.stop_button.setDisabled(True)

    @Slot()
    def stop_button_click_handler(self):
        """Method to handle what to do when the stop button is pressed"""
        self.stop_button.setDisabled(True)
        # Interrupt computations or stop debugging
        if not self.shellwidget.is_waiting_pdb_input():
            self.interrupt_kernel()
        else:
            self.shellwidget.pdb_execute('exit', hidden=True)

    def show_kernel_error(self, error):
        """Show kernel initialization errors in infowidget."""
        # Replace end of line chars with <br>
        eol = sourcecode.get_eol_chars(error)
        if eol:
            error = error.replace(eol, '<br>')

        # Don't break lines in hyphens
        # From https://stackoverflow.com/q/7691569/438386
        error = error.replace('-', '&#8209')

        # Create error page
        message = _("An error ocurred while starting the kernel")
        kernel_error_template = Template(KERNEL_ERROR)
        self.info_page = kernel_error_template.substitute(
            css_path=self.css_path,
            message=message,
            error=error)

        # Show error
        self.set_info_page()
        self.shellwidget.hide()
        self.infowidget.show()

        # Tell the client we're in error mode
        self.is_error_shown = True

    def get_name(self):
        """Return client name"""
        if self.given_name is None:
            # Name according to host
            if self.hostname is None:
                name = _("Console")
            else:
                name = self.hostname
            # Adding id to name
            client_id = self.id_['int_id'] + u'/' + self.id_['str_id']
            name = name + u' ' + client_id
        elif self.given_name in ["Pylab", "SymPy", "Cython"]:
            client_id = self.id_['int_id'] + u'/' + self.id_['str_id']
            name = self.given_name + u' ' + client_id
        else:
            name = self.given_name + u'/' + self.id_['str_id']
        return name

    def get_control(self):
        """Return the text widget (or similar) to give focus to"""
        # page_control is the widget used for paging
        page_control = self.shellwidget._page_control
        if page_control and page_control.isVisible():
            return page_control
        else:
            return self.shellwidget._control

    def get_kernel(self):
        """Get kernel associated with this client"""
        return self.shellwidget.kernel_manager

    def get_options_menu(self):
        """Return options menu"""
        env_action = create_action(
                        self,
                        _("Show environment variables"),
                        icon=ima.icon('environ'),
                        triggered=self.shellwidget.request_env
                     )

        syspath_action = create_action(
                            self,
                            _("Show sys.path contents"),
                            icon=ima.icon('syspath'),
                            triggered=self.shellwidget.request_syspath
                         )

        self.show_time_action.setChecked(self.show_elapsed_time)
        additional_actions = [MENU_SEPARATOR,
                              env_action,
                              syspath_action,
                              self.show_time_action]

        if self.menu_actions is not None:
            console_menu = self.menu_actions + additional_actions
            return console_menu

        else:
            return additional_actions

    def get_toolbar_buttons(self):
        """Return toolbar buttons list."""
        buttons = []

        # Code to add the stop button
        if self.stop_button is None:
            self.stop_button = create_toolbutton(
                                   self,
                                   text=_("Stop"),
                                   icon=self.stop_icon,
                                   tip=_("Stop the current command"))
            self.disable_stop_button()
            # set click event handler
            self.stop_button.clicked.connect(self.stop_button_click_handler)
            if is_dark_interface():
                self.stop_button.setStyleSheet("QToolButton{padding: 3px;}")
        if self.stop_button is not None:
            buttons.append(self.stop_button)

        # Reset namespace button
        if self.reset_button is None:
            self.reset_button = create_toolbutton(
                                    self,
                                    text=_("Remove"),
                                    icon=ima.icon('editdelete'),
                                    tip=_("Remove all variables"),
                                    triggered=self.reset_namespace)
            if is_dark_interface():
                self.reset_button.setStyleSheet("QToolButton{padding: 3px;}")
        if self.reset_button is not None:
            buttons.append(self.reset_button)

        if self.options_button is None:
            options = self.get_options_menu()
            if options:
                self.options_button = create_toolbutton(self,
                        text=_('Options'), icon=ima.icon('tooloptions'))
                self.options_button.setPopupMode(QToolButton.InstantPopup)
                menu = QMenu(self)
                add_actions(menu, options)
                self.options_button.setMenu(menu)
        if self.options_button is not None:
            buttons.append(self.options_button)

        return buttons

    def add_actions_to_context_menu(self, menu):
        """Add actions to IPython widget context menu"""
        inspect_action = create_action(
            self,
            _("Inspect current object"),
            QKeySequence(CONF.get_shortcut('console',
                                           'inspect current object')),
            icon=ima.icon('MessageBoxInformation'),
            triggered=self.inspect_object)

        clear_line_action = create_action(
            self,
            _("Clear line or block"),
            QKeySequence(CONF.get_shortcut('console', 'clear line')),
            triggered=self.clear_line)

        reset_namespace_action = create_action(
            self,
            _("Remove all variables"),
            QKeySequence(CONF.get_shortcut('ipython_console',
                                           'reset namespace')),
            icon=ima.icon('editdelete'),
            triggered=self.reset_namespace)

        clear_console_action = create_action(
            self,
            _("Clear console"),
            QKeySequence(CONF.get_shortcut('console', 'clear shell')),
            triggered=self.clear_console)

        quit_action = create_action(
            self,
            _("&Quit"),
            icon=ima.icon('exit'),
            triggered=self.exit_callback)

        add_actions(menu, (None, inspect_action, clear_line_action,
                           clear_console_action, reset_namespace_action,
                           None, quit_action))
        return menu

    def set_font(self, font):
        """Set IPython widget's font"""
        self.shellwidget._control.setFont(font)
        self.shellwidget.font = font

    def set_color_scheme(self, color_scheme, reset=True):
        """Set IPython color scheme."""
        # Needed to handle not initialized kernel_client
        # See spyder-ide/spyder#6996.
        try:
            self.shellwidget.set_color_scheme(color_scheme, reset)
        except AttributeError:
            pass

    def shutdown(self):
        """Shutdown kernel"""
        if self.get_kernel() is not None and not self.slave:
            self.shellwidget.spyder_kernel_comm.close()
            self.shellwidget.spyder_kernel_comm.shutdown_comm_channel()
            self.shellwidget._pdb_history_file.save_thread.stop()
            self.shellwidget.kernel_manager.stop_restarter()
        self.shutdown_thread = QThread()
        self.shutdown_thread.run = self.finalize_shutdown
        self.shutdown_thread.finished.connect(self.stop_kernel_channels)
        self.shutdown_thread.start()

    def finalize_shutdown(self):
        """Finalise the shutdown."""
        if self.get_kernel() is not None and not self.slave:
            self.shellwidget.kernel_manager.shutdown_kernel()

    def stop_kernel_channels(self):
        """Stop kernel channels."""
        if self.shellwidget.kernel_client is not None:
            self.shellwidget.kernel_client.stop_channels()

    def interrupt_kernel(self):
        """Interrupt the associanted Spyder kernel if it's running"""
        # Needed to prevent a crash when a kernel is not running.
        # See spyder-ide/spyder#6299.
        try:
            self.shellwidget.request_interrupt_kernel()
        except RuntimeError:
            pass

    @Slot()
    def restart_kernel(self):
        """
        Restart the associated kernel.

        Took this code from the qtconsole project
        Licensed under the BSD license
        """
        sw = self.shellwidget

        if not running_under_pytest() and self.ask_before_restart:
            message = _('Are you sure you want to restart the kernel?')
            buttons = QMessageBox.Yes | QMessageBox.No
            result = QMessageBox.question(self, _('Restart kernel?'),
                                          message, buttons)
        else:
            result = None

        if (result == QMessageBox.Yes or
                running_under_pytest() or
                not self.ask_before_restart):
            if sw.kernel_manager:
                if self.infowidget.isVisible():
                    self.infowidget.hide()
                    sw.show()
                try:
                    # Close comm
                    sw.spyder_kernel_comm.close()
                    sw.kernel_manager.restart_kernel(
                        stderr=self.stderr_handle)
                    # Reopen comm
                    sw.spyder_kernel_comm.open_comm(sw.kernel_client)

                except RuntimeError as e:
                    sw._append_plain_text(
                        _('Error restarting kernel: %s\n') % e,
                        before_prompt=True
                    )
                else:
                    # For spyder-ide/spyder#6235, IPython was changing the
                    # setting of %colors on windows by assuming it was using a
                    # dark background. This corrects it based on the scheme.
                    self.set_color_scheme(sw.syntax_style)
                    sw._append_html(_("<br>Restarting kernel...\n<hr><br>"),
                                    before_prompt=False)
            else:
                sw._append_plain_text(
                    _('Cannot restart a kernel not started by Spyder\n'),
                    before_prompt=True
                )

    @Slot(str)
    def kernel_restarted_message(self, msg):
        """Show kernel restarted/died messages."""
        if not self.is_error_shown:
            # If there are kernel creation errors, jupyter_client will
            # try to restart the kernel and qtconsole prints a
            # message about it.
            # So we read the kernel's stderr_file and display its
            # contents in the client instead of the usual message shown
            # by qtconsole.
            try:
                stderr = self._read_stderr()
            except Exception:
                stderr = None
            if stderr:
                self.show_kernel_error('<tt>%s</tt>' % stderr)
        else:
            self.shellwidget._append_html("<br>%s<hr><br>" % msg,
                                          before_prompt=False)

    @Slot()
    def inspect_object(self):
        """Show how to inspect an object with our Help plugin"""
        self.shellwidget._control.inspect_current_object()

    @Slot()
    def clear_line(self):
        """Clear a console line"""
        self.shellwidget._keyboard_quit()

    @Slot()
    def clear_console(self):
        """Clear the whole console"""
        self.shellwidget.clear_console()

    @Slot()
    def reset_namespace(self):
        """Resets the namespace by removing all names defined by the user"""
        self.shellwidget.reset_namespace(warning=self.reset_warning,
                                         message=True)

    def update_history(self):
        self.history = self.shellwidget._history

    @Slot(object)
    def show_syspath(self, syspath):
        """Show sys.path contents."""
        if syspath is not None:
            editor = CollectionsEditor(self)
            editor.setup(syspath, title="sys.path contents", readonly=True,
                         icon=ima.icon('syspath'))
            self.dialog_manager.show(editor)
        else:
            return

    @Slot(object)
    def show_env(self, env):
        """Show environment variables."""
        self.dialog_manager.show(RemoteEnvDialog(env, parent=self))

    def create_time_label(self):
        """Create elapsed time label widget (if necessary) and return it"""
        if self.time_label is None:
            self.time_label = QLabel()
        return self.time_label

    def show_time(self, end=False):
        """Text to show in time_label."""
        if self.time_label is None:
            return

        elapsed_time = time.monotonic() - self.t0
        # System time changed to past date, so reset start.
        if elapsed_time < 0:
            self.t0 = time.monotonic()
            elapsed_time = 0
        if elapsed_time > 24 * 3600:  # More than a day...!
            fmt = "%d %H:%M:%S"
        else:
            fmt = "%H:%M:%S"
        if end:
            color = "#AAAAAA"
        else:
            color = "#AA6655"
        text = "<span style=\'color: %s\'><b>%s" \
               "</b></span>" % (color,
                                time.strftime(fmt, time.gmtime(elapsed_time)))
        self.time_label.setText(text)

    def update_time_label_visibility(self):
        """Update elapsed time visibility."""
        self.time_label.setVisible(self.show_elapsed_time)

    @Slot(bool)
    def set_elapsed_time_visible(self, state):
        """Slot to show/hide elapsed time label."""
        self.show_elapsed_time = state
        if self.time_label is not None:
            self.time_label.setVisible(state)

    def set_info_page(self):
        """Set current info_page."""
        if self.info_page is not None:
            self.infowidget.setHtml(
                self.info_page,
                QUrl.fromLocalFile(self.css_path)
            )

    #------ Private API -------------------------------------------------------
    def _create_loading_page(self):
        """Create html page to show while the kernel is starting"""
        loading_template = Template(LOADING)
        loading_img = get_image_path('loading_sprites.png')
        if os.name == 'nt':
            loading_img = loading_img.replace('\\', '/')
        message = _("Connecting to kernel...")
        page = loading_template.substitute(css_path=self.css_path,
                                           loading_img=loading_img,
                                           message=message)
        return page

    def _create_blank_page(self):
        """Create html page to show while the kernel is starting"""
        loading_template = Template(BLANK)
        page = loading_template.substitute(css_path=self.css_path)
        return page

    def _show_loading_page(self):
        """Show animation while the kernel is loading."""
        self.shellwidget.hide()
        self.infowidget.show()
        self.info_page = self.loading_page
        self.set_info_page()

    def _hide_loading_page(self):
        """Hide animation shown while the kernel is loading."""
        self.infowidget.hide()
        self.shellwidget.show()
        self.info_page = self.blank_page
        self.set_info_page()
        self.shellwidget.sig_prompt_ready.disconnect(self._hide_loading_page)

    def _read_stderr(self):
        """Read the stderr file of the kernel."""
        # We need to read stderr_file as bytes to be able to
        # detect its encoding with chardet
        f = open(self.stderr_file, 'rb')

        try:
            stderr_text = f.read()

            # This is needed to avoid showing an empty error message
            # when the kernel takes too much time to start.
            # See spyder-ide/spyder#8581.
            if not stderr_text:
                return ''

            # This is needed since the stderr file could be encoded
            # in something different to utf-8.
            # See spyder-ide/spyder#4191.
            encoding = get_coding(stderr_text)
            stderr_text = to_text_string(stderr_text, encoding)
            return stderr_text
        finally:
            f.close()

    def _show_mpl_backend_errors(self):
        """
        Show possible errors when setting the selected Matplotlib backend.
        """
        if not self.external_kernel:
            self.shellwidget.call_kernel().show_mpl_backend_errors()
        self.shellwidget.sig_prompt_ready.disconnect(
            self._show_mpl_backend_errors)