def execute_new_loop(self, signal):
        """Starts the new event loop and pass `signal` in it.

        This is required for processing a modal screens.

        :param signal: Signal passed to the new event loop.
        :type signal: The `AbstractSignal` based class.
        """
        super().execute_new_loop(signal)

        if self._force_quit:
            return

        self._active_queue = EventQueue()

        # TODO: Remove when python3-astroid 1.5.3 will be in Fedora
        # pylint: disable=not-context-manager
        with self._lock:
            self._event_queues.append(self._active_queue)

        self.enqueue_signal(signal)
        self._mainloop()
        log.debug("Inner loop is closed")
Exemplo n.º 2
0
 def setUp(self):
     self.e = EventQueue()
Exemplo n.º 3
0
class EventQueue_TestCase(unittest.TestCase):
    def setUp(self):
        self.e = EventQueue()

    def test_queue_is_empty(self):
        self.assertTrue(self.e.empty())

    def test_enqueue(self):
        fake_signal = MagicMock()

        self.e.enqueue(fake_signal)
        self.assertFalse(self.e.empty())

        self.assertEqual(fake_signal, self.e.get())
        self.assertTrue(self.e.empty())

    def test_enqueue_priority(self):
        signal_low_priority = TestSignal(priority=10)
        signal_high_priority = TestSignal(priority=0)

        self.e.enqueue(signal_low_priority)
        self.e.enqueue(signal_high_priority)

        self.assertEqual(signal_high_priority, self.e.get())
        self.assertEqual(signal_low_priority, self.e.get())

        # Test adding signals in different order (result shouldn't change)
        self.e.enqueue(signal_high_priority)
        self.e.enqueue(signal_low_priority)

        self.assertEqual(signal_high_priority, self.e.get())
        self.assertEqual(signal_low_priority, self.e.get())

    def test_adding_event_source(self):
        fake_source = MagicMock()
        self.e.add_source(fake_source)

        self.assertTrue(self.e.contains_source(fake_source))

    def test_removing_event_source(self):
        fake_source = MagicMock()
        self.e.add_source(fake_source)

        self.e.remove_source(fake_source)

        self.assertFalse(self.e.contains_source(fake_source))

    def test_remove_empty_source(self):
        with self.assertRaises(EventQueueError):
            self.e.remove_source(MagicMock())

    def test_enqueue_if_source_belongs(self):
        source = MagicMock()
        signal = TestSignal(source=source)

        self.e.add_source(source)
        self.assertTrue(self.e.enqueue_if_source_belongs(signal, source))
        self.assertEqual(signal, self.e.get())

    def test_enqueue_if_source_does_not_belong(self):
        signal = TestSignal()
        signal_low_priority = TestSignal(priority=25)

        # the get method will wait if nothing present so adding low priority signal below give us check if the queue
        # is really empty
        self.e.enqueue(signal_low_priority)

        self.assertFalse(self.e.enqueue_if_source_belongs(signal, MagicMock()))
        self.assertEqual(signal_low_priority, self.e.get())
 def __init__(self):
     super().__init__()
     self._active_queue = EventQueue()
     self._event_queues = [self._active_queue]
     self._lock = Lock()
class MainLoop(AbstractEventLoop):
    """Default main event loop for the Simpleline.

    This event loop can be replaced by your event loop by implementing `simpleline.event_loop.AbstractEventLoop` class.
    """
    def __init__(self):
        super().__init__()
        self._active_queue = EventQueue()
        self._event_queues = [self._active_queue]
        self._lock = Lock()

    def register_signal_source(self, signal_source):
        """Register source of signal for actual event queue.

        :param signal_source: Source for future signals.
        :type signal_source: `simpleline.render.ui_screen.UIScreen`.
        """
        super().register_signal_source(signal_source)
        self._active_queue.add_source(signal_source)

    def run(self):
        """This methods starts the application.

        Do not use self.mainloop() directly as run() handles all the required exceptions
        needed to keep nested mainloop working.
        """
        super().run()
        self._run_loop = True

        try:
            self._mainloop()
        except ExitMainLoop:
            pass

        log.debug("Main loop ended. Running callback if set.")

        if self._quit_callback:
            cb = self._quit_callback.callback
            cb(self._quit_callback.args)

    def force_quit(self):
        """Force quit all running event loops.

        Kill all loop including inner loops (modal window).
        None of the Simpleline events will be processed anymore.
        """
        super().force_quit()
        self._event_queues.clear()
        self._run_loop = False

    def execute_new_loop(self, signal):
        """Starts the new event loop and pass `signal` in it.

        This is required for processing a modal screens.

        :param signal: Signal passed to the new event loop.
        :type signal: The `AbstractSignal` based class.
        """
        super().execute_new_loop(signal)

        if self._force_quit:
            return

        self._active_queue = EventQueue()

        # TODO: Remove when python3-astroid 1.5.3 will be in Fedora
        # pylint: disable=not-context-manager
        with self._lock:
            self._event_queues.append(self._active_queue)

        self.enqueue_signal(signal)
        self._mainloop()
        log.debug("Inner loop is closed")

    def close_loop(self):
        """Close active event loop.

        Close an event loop created by the `execute_new_loop()` method.
        """
        super().close_loop()
        self.process_signals()

        # TODO: Remove when python3-astroid 1.5.3 will be in Fedora
        # pylint: disable=not-context-manager
        with self._lock:
            self._event_queues.pop()
            try:
                self._active_queue = self._event_queues[-1]
            except IndexError:
                log.error("No more event queues to work with!")
                raise ExitMainLoop()

        self._run_loop = False

    def enqueue_signal(self, signal):
        """Enqueue new event for processing.

        Enqueue signal to the most inner queue (nearest to the active queue) where the `signal.source` belongs.
        If it belongs nowhere enqueue it to the active one.

        This method is thread safe.

        :param signal: Event which you want to add to the event queue for processing.
        :type signal: Instance based on AbstractEvent class.
        """
        if self._force_quit:
            return

        super().enqueue_signal(signal)
        # TODO: Remove when python3-astroid 1.5.3 will be in Fedora
        # pylint: disable=not-context-manager
        with self._lock:
            for queue in reversed(self._event_queues):
                if queue.enqueue_if_source_belongs(signal, signal.source):
                    return

        self._active_queue.enqueue(signal)

    def _mainloop(self):
        """Single mainloop. Do not use directly, start the application using run()."""
        # run infinite loop
        # this will always wait on input processing or similar so it should not busy waiting
        while self._run_loop:
            self._process_signals_loop()

        if not self._force_quit:
            # set back to True to leave outer loop working
            self._run_loop = True

    def process_signals(self, return_after=None):
        """This method processes incoming async messages.

        Process signals enqueued by the `self.enqueue_signal()` method. Call handlers registered to the signals by
        the `self.register_signal_handler()` method.

        When `return_after` is specified then wait to the point when this signal is processed.
        NO warranty that this method will return immediately after the signal was processed!

        Without `return_after` parameter this method will return after all queued signals with the highest priority
        will be processed.

        The method is NOT thread safe!

        :param return_after: Wait on this signal to be processed.
        :type return_after: Class of the signal.
        """
        super().process_signals(return_after)
        if return_after is not None:
            self._process_signals_with_return(return_after)
        else:
            self._process_signals_iteration()

    def _process_signals_with_return(self, return_after):
        """Process signals until the return_after signal was processed.

        Or the loop quited.
        """
        # get unique ID when waiting for the signal
        unique_id = self._register_wait_on_signal(return_after)

        while self._run_loop:
            signal = self._active_queue.get()

            # do the signal processing (call handlers)
            self._process_signal(signal)

            # was our signal processed if yes, return this method
            if self._check_if_signal_processed(return_after, unique_id):
                return

    def _process_signals_iteration(self):
        """Process queued signal and then return."""
        priority = None

        while not self._active_queue.empty() and self._run_loop:
            if priority is None:
                # take first signal to find out the highest priority in queue
                signal = self._active_queue.get()
                priority = signal.priority
            else:
                # get signal with this priority only
                signal = self._active_queue.get_top_event_if_priority(priority)

            # Signal with this priority is not available anymore
            if signal is None:
                return

            self._process_signal(signal)

    def _process_signals_loop(self):
        """Process signal until the event loop quited."""
        while self._run_loop:
            signal = self._active_queue.get()
            self._process_signal(signal)

    def _process_signal(self, signal):
        log.debug("Processing signal %s", signal)

        self._mark_signal_processed(signal)

        if type(signal) in self._handlers:
            for handler_data in self._handlers[type(signal)]:
                try:
                    handler_data.callback(signal, handler_data.data)
                except ExitMainLoop:  # pylint: disable=try-except-raise
                    raise
                except Exception:  # pylint: disable=broad-except
                    self.enqueue_signal(ExceptionSignal(self))
        elif type(signal) is ExceptionSignal:
            self.kill_app_with_traceback(signal)