def test_lifecycle_real(self):
        """Testing common life-cycle with existing and non-existant collections. This
        test uses the real Anki objects and actually creates a new collection on disk."""

        wrapper = CollectionWrapper(self.collection_path)
        self.assertFalse(os.path.exists(self.collection_path))
        self.assertFalse(wrapper.opened())

        wrapper.open()
        self.assertTrue(os.path.exists(self.collection_path))
        self.assertTrue(wrapper.opened())

        # calling open twice shouldn't break anything
        wrapper.open()

        wrapper.close()
        self.assertTrue(os.path.exists(self.collection_path))
        self.assertFalse(wrapper.opened())

        # open the same collection again (not a creation)
        wrapper = CollectionWrapper(self.collection_path)
        self.assertFalse(wrapper.opened())
        wrapper.open()
        self.assertTrue(wrapper.opened())
        wrapper.close()
        self.assertFalse(wrapper.opened())
        self.assertTrue(os.path.exists(self.collection_path))
Exemplo n.º 2
0
class ThreadingCollectionWrapper(object):
    """Provides the same interface as CollectionWrapper, but it creates a new Thread to 
    interact with the collection."""
    def __init__(self, path, setup_new_collection=None):
        self.path = path
        self.wrapper = CollectionWrapper(path, setup_new_collection)

        self._queue = Queue()
        self._thread = None
        self._running = False
        self.last_timestamp = time.time()

        self.start()

    @property
    def running(self):
        return self._running

    def qempty(self):
        return self._queue.empty()

    def current(self):
        from threading import current_thread
        return current_thread() == self._thread

    def execute(self, func, args=[], kw={}, waitForReturn=True):
        """ Executes a given function on this thread with the *args and **kw.

        If 'waitForReturn' is True, then it will block until the function has
        executed and return its return value.  If False, it will return None
        immediately and the function will be executed sometime later.
        """

        if waitForReturn:
            return_queue = Queue()
        else:
            return_queue = None

        self._queue.put((func, args, kw, return_queue))

        if return_queue is not None:
            ret = return_queue.get(True)
            if isinstance(ret, Exception):
                raise ret
            return ret

    def _run(self):
        logging.info('CollectionThread[%s]: Starting...', self.path)

        try:
            while self._running:
                func, args, kw, return_queue = self._queue.get(True)

                if hasattr(func, 'func_name'):
                    func_name = func.func_name
                else:
                    func_name = func.__class__.__name__

                logging.info('CollectionThread[%s]: Running %s(*%s, **%s)',
                             self.path, func_name, repr(args), repr(kw))
                self.last_timestamp = time.time()

                try:
                    ret = self.wrapper.execute(func, args, kw, return_queue)
                except Exception as e:
                    logging.error(
                        'CollectionThread[%s]: Unable to %s(*%s, **%s): %s',
                        self.path,
                        func_name,
                        repr(args),
                        repr(kw),
                        e,
                        exc_info=True)
                    # we return the Exception which will be raise'd on the other end
                    ret = e

                if return_queue is not None:
                    return_queue.put(ret)
        except Exception as e:
            logging.error(
                'CollectionThread[%s]: Thread crashed! Exception: %s',
                self.path,
                e,
                exc_info=True)
        finally:
            self.wrapper.close()
            # clean out old thread object
            self._thread = None
            # in case we got here via an exception
            self._running = False

            logging.info('CollectionThread[%s]: Stopped!', self.path)

    def start(self):
        if not self._running:
            self._running = True
            assert self._thread is None
            self._thread = Thread(target=self._run)
            self._thread.start()

    def stop(self):
        def _stop(col):
            self._running = False

        self.execute(_stop, waitForReturn=False)

    def stop_and_wait(self):
        """ Tell the thread to stop and wait for it to happen. """
        self.stop()
        if self._thread is not None:
            self._thread.join()

    #
    # Mimic the CollectionWrapper interface
    #

    def open(self):
        """Non-op. The collection will be opened on demand."""
        pass

    def close(self):
        """Closes the underlying collection without stopping the thread."""
        def _close(col):
            self.wrapper.close()

        self.execute(_close, waitForReturn=False)

    def opened(self):
        return self.wrapper.opened()