From 356ea8fff80cb2f9836a717b1c6712575b413cd9 Mon Sep 17 00:00:00 2001 From: Andreas Preikschat Date: Sat, 25 Jun 2011 06:42:49 +0200 Subject: [PATCH 1/5] - added an 'intelligent' image queue with priorities - resize image in the thread - change instances variables (based on qt) --- openlp/core/lib/imagemanager.py | 170 +++++++++++++++++++++----------- 1 file changed, 114 insertions(+), 56 deletions(-) diff --git a/openlp/core/lib/imagemanager.py b/openlp/core/lib/imagemanager.py index d89cefccc..bab9748d0 100644 --- a/openlp/core/lib/imagemanager.py +++ b/openlp/core/lib/imagemanager.py @@ -32,6 +32,7 @@ to wait for the conversion to happen. """ import logging import time +import Queue from PyQt4 import QtCore @@ -53,15 +54,59 @@ class ImageThread(QtCore.QThread): """ Run the thread. """ - self.imageManager.process() + self.imageManager._process() + + +class Priority(object): + """ + Enumeration class for different priorities. + + ``Low`` + Only the image's byte stream has to be generated. Neither the QImage nor + the byte stream has been requested yet. + + ``Normal`` + The image's byte stream as well as the image has to be generated. + Neither the QImage nor the byte stream has been requested yet. + + ``High`` + The image's byte stream as well as the image has to be generated. The + QImage for this image has been requested. + + ``Urgent`` + The image's byte stream as well as the image has to be generated. The + byte stream for this image has been requested. + """ + Low = 3 + Normal = 2 + High = 1 + Urgent = 0 class Image(object): - name = '' - path = '' - dirty = True - image = None - image_bytes = None + def __init__(self, name='', path=''): + self.name = name + self.path = path + self.image = None + self.image_bytes = None + self.priority = Priority.Normal + + +class PriorityQueue(Queue.PriorityQueue): + """ + Customised ``Queue.PriorityQueue``. + """ + def remove(self, item): + """ + Removes the given ``item`` from the queue. + + ``item`` + The item to remove. This should be a tuple:: + + ``(Priority, Image)`` + """ + if item in self.queue: + self.queue.remove(item) class ImageManager(QtCore.QObject): @@ -76,50 +121,64 @@ class ImageManager(QtCore.QObject): self.width = current_screen[u'size'].width() self.height = current_screen[u'size'].height() self._cache = {} - self._thread_running = False - self._cache_dirty = False - self.image_thread = ImageThread(self) + self._imageThread = ImageThread(self) + self._clean_queue = PriorityQueue() def update_display(self): """ - Screen has changed size so rebuild the cache to new size + Screen has changed size so rebuild the cache to new size. """ log.debug(u'update_display') current_screen = ScreenList.get_instance().current self.width = current_screen[u'size'].width() self.height = current_screen[u'size'].height() - # mark the images as dirty for a rebuild - for key in self._cache.keys(): - image = self._cache[key] - image.dirty = True - image.image = resize_image(image.path, self.width, self.height) - self._cache_dirty = True - # only one thread please - if not self._thread_running: - self.image_thread.start() + # Mark the images as dirty for a rebuild by setting the image and byte + # stream to None. + self._clean_queue = PriorityQueue() + for key, image in self._cache.iteritems(): + image.priority = Priority.Normal + image.image = None + image.image_bytes = None + self._clean_queue.put((image.priority, image)) + # We want only one thread. + if not self._imageThread.isRunning(): + self._imageThread.start() def get_image(self, name): """ - Return the Qimage from the cache + Return the ``QImage`` from the cache. If not present wait for the + background thread to process it. """ log.debug(u'get_image %s' % name) - return self._cache[name].image + image = self._cache[name] + if image.image is None: + self._clean_queue.remove((image.priority, image)) + image.priority = Priority.High + self._clean_queue.put((image.priority, image)) + while image.image is None: + log.debug(u'get_image - waiting') + time.sleep(0.1) + return image.image def get_image_bytes(self, name): """ - Returns the byte string for an image - If not present wait for the background thread to process it. + Returns the byte string for an image. If not present wait for the + background thread to process it. """ log.debug(u'get_image_bytes %s' % name) - if not self._cache[name].image_bytes: - while self._cache[name].dirty: + image = self._cache[name] + if image.image_bytes is None: + self._clean_queue.remove((image.priority, image)) + image.priority = Priority.Urgent + self._clean_queue.put((image.priority, image)) + while image.image_bytes is None: log.debug(u'get_image_bytes - waiting') time.sleep(0.1) - return self._cache[name].image_bytes + return image.image_bytes def del_image(self, name): """ - Delete the Image from the Cache + Delete the Image from the cache. """ log.debug(u'del_image %s' % name) if name in self._cache: @@ -127,45 +186,44 @@ class ImageManager(QtCore.QObject): def add_image(self, name, path): """ - Add image to cache if it is not already there + Add image to cache if it is not already there. """ log.debug(u'add_image %s:%s' % (name, path)) if not name in self._cache: - image = Image() - image.name = name - image.path = path - image.image = resize_image(path, self.width, self.height) + image = Image(name, path) self._cache[name] = image + self._clean_queue.put((image.priority, image)) else: log.debug(u'Image in cache %s:%s' % (name, path)) - self._cache_dirty = True - # only one thread please - if not self._thread_running: - self.image_thread.start() + # We want only one thread. + if not self._imageThread.isRunning(): + self._imageThread.start() - def process(self): + def _process(self): """ - Controls the processing called from a QThread + Controls the processing called from a ``QtCore.QThread``. """ - log.debug(u'process - started') - self._thread_running = True - self.clean_cache() - # data loaded since we started ? - while self._cache_dirty: - log.debug(u'process - recycle') - self.clean_cache() - self._thread_running = False - log.debug(u'process - ended') + log.debug(u'_process - started') + while not self._clean_queue.empty(): + self._clean_cache() + log.debug(u'_process - ended') - def clean_cache(self): + def _clean_cache(self): """ Actually does the work. """ - log.debug(u'clean_cache') - # we will clean the cache now - self._cache_dirty = False - for key in self._cache.keys(): - image = self._cache[key] - if image.dirty: - image.image_bytes = image_to_byte(image.image) - image.dirty = False + log.debug(u'_clean_cache') + image = self._clean_queue.get()[1] + # Generate the QImage for the image. + if image.image is None: + image.image = resize_image(image.path, self.width, self.height) + # If the priority is not urgent, then set the priority to low and + # do not start to generate the byte stream. + if image.priority != Priority.Urgent: + self._clean_queue.remove((image.priority, image)) + image.priority = Priority.Low + self._clean_queue.put((image.priority, image)) + return + # Generate the byte stream for the image. + if image.image_bytes is None: + image.image_bytes = image_to_byte(image.image) From 1730f5a99f8c84b8aca73ca05ab4cdcff72f622d Mon Sep 17 00:00:00 2001 From: Andreas Preikschat Date: Sat, 25 Jun 2011 07:34:07 +0200 Subject: [PATCH 2/5] added new priority and fixed comments --- openlp/core/lib/imagemanager.py | 43 ++++++++++++++++++++++++++------- 1 file changed, 34 insertions(+), 9 deletions(-) diff --git a/openlp/core/lib/imagemanager.py b/openlp/core/lib/imagemanager.py index bab9748d0..d445545a7 100644 --- a/openlp/core/lib/imagemanager.py +++ b/openlp/core/lib/imagemanager.py @@ -43,8 +43,8 @@ log = logging.getLogger(__name__) class ImageThread(QtCore.QThread): """ - A special Qt thread class to speed up the display of text based frames. - This is threaded so it loads the frames in background + A special Qt thread class to speed up the display of images. This is + threaded so it loads the frames and generates byte stream in background. """ def __init__(self, manager): QtCore.QThread.__init__(self, None) @@ -61,22 +61,33 @@ class Priority(object): """ Enumeration class for different priorities. + ``Lowest`` + Only the image's byte stream has to be generated. But neither the + ``QImage`` nor the byte stream has been requested yet. + ``Low`` - Only the image's byte stream has to be generated. Neither the QImage nor - the byte stream has been requested yet. + Only the image's byte stream has to be generated. Because the image's + ``QImage`` has been requested previously it is reasonable to assume that + the byte stream will be needed before the byte stream of other images + which ``QImage`` were not generated due to a request. ``Normal`` The image's byte stream as well as the image has to be generated. - Neither the QImage nor the byte stream has been requested yet. + Neither the ``QImage`` nor the byte stream has been requested yet. ``High`` The image's byte stream as well as the image has to be generated. The - QImage for this image has been requested. + ``QImage`` for this image has been requested. + **Note**, this priority is only set when the ``QImage`` has not been + generated yet. ``Urgent`` The image's byte stream as well as the image has to be generated. The byte stream for this image has been requested. + **Note**, this priority is only set when the byte stream has not been + generated yet. """ + Lowest = 4 Low = 3 Normal = 2 High = 1 @@ -84,6 +95,11 @@ class Priority(object): class Image(object): + """ + This class represents an image. To mark an image as *dirty* set the instance + variables ``image`` and ``image_bytes`` to ``None`` and add the image object + to the queue of images to process. + """ def __init__(self, name='', path=''): self.name = name self.path = path @@ -217,9 +233,18 @@ class ImageManager(QtCore.QObject): # Generate the QImage for the image. if image.image is None: image.image = resize_image(image.path, self.width, self.height) - # If the priority is not urgent, then set the priority to low and - # do not start to generate the byte stream. - if image.priority != Priority.Urgent: + # Set the priority to Lowest and stop here as we need to process + # more important images first. + if image.priority == Priority.Normal: + self._clean_queue.remove((image.priority, image)) + image.priority = Priority.Lowest + self._clean_queue.put((image.priority, image)) + return + # For image with high priority we set the priority to Low, as the + # byte stream might be needed earlier the byte stream of image with + # Normal priority. We stop here as we need to process more important + # images first. + elif image.priority == Priority.High: self._clean_queue.remove((image.priority, image)) image.priority = Priority.Low self._clean_queue.put((image.priority, image)) From b1b29e9abfa8148c2eb5861a119fdd5b8a7fd5d9 Mon Sep 17 00:00:00 2001 From: Andreas Preikschat Date: Sat, 25 Jun 2011 08:20:02 +0200 Subject: [PATCH 3/5] comment fix --- openlp/core/lib/imagemanager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openlp/core/lib/imagemanager.py b/openlp/core/lib/imagemanager.py index d445545a7..3eace2a6a 100644 --- a/openlp/core/lib/imagemanager.py +++ b/openlp/core/lib/imagemanager.py @@ -69,7 +69,7 @@ class Priority(object): Only the image's byte stream has to be generated. Because the image's ``QImage`` has been requested previously it is reasonable to assume that the byte stream will be needed before the byte stream of other images - which ``QImage`` were not generated due to a request. + whose ``QImage`` were not generated due to a request. ``Normal`` The image's byte stream as well as the image has to be generated. From f9792337877274f48a838978a5985e7478f32620 Mon Sep 17 00:00:00 2001 From: Andreas Preikschat Date: Sat, 25 Jun 2011 21:37:23 +0200 Subject: [PATCH 4/5] renamed method --- openlp/core/lib/imagemanager.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openlp/core/lib/imagemanager.py b/openlp/core/lib/imagemanager.py index 3eace2a6a..ea8295f23 100644 --- a/openlp/core/lib/imagemanager.py +++ b/openlp/core/lib/imagemanager.py @@ -221,14 +221,14 @@ class ImageManager(QtCore.QObject): """ log.debug(u'_process - started') while not self._clean_queue.empty(): - self._clean_cache() + self._process_cache() log.debug(u'_process - ended') - def _clean_cache(self): + def _process_cache(self): """ Actually does the work. """ - log.debug(u'_clean_cache') + log.debug(u'_process_cache') image = self._clean_queue.get()[1] # Generate the QImage for the image. if image.image is None: From e50ca971d657988005a3b973fe62495b79eb0faf Mon Sep 17 00:00:00 2001 From: Andreas Preikschat Date: Mon, 27 Jun 2011 13:28:58 +0200 Subject: [PATCH 5/5] change queue name; remove image from queue when deleted from cache --- openlp/core/lib/imagemanager.py | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/openlp/core/lib/imagemanager.py b/openlp/core/lib/imagemanager.py index ea8295f23..5970efd4f 100644 --- a/openlp/core/lib/imagemanager.py +++ b/openlp/core/lib/imagemanager.py @@ -138,7 +138,7 @@ class ImageManager(QtCore.QObject): self.height = current_screen[u'size'].height() self._cache = {} self._imageThread = ImageThread(self) - self._clean_queue = PriorityQueue() + self._conversion_queue = PriorityQueue() def update_display(self): """ @@ -150,12 +150,12 @@ class ImageManager(QtCore.QObject): self.height = current_screen[u'size'].height() # Mark the images as dirty for a rebuild by setting the image and byte # stream to None. - self._clean_queue = PriorityQueue() + self._conversion_queue = PriorityQueue() for key, image in self._cache.iteritems(): image.priority = Priority.Normal image.image = None image.image_bytes = None - self._clean_queue.put((image.priority, image)) + self._conversion_queue.put((image.priority, image)) # We want only one thread. if not self._imageThread.isRunning(): self._imageThread.start() @@ -168,9 +168,9 @@ class ImageManager(QtCore.QObject): log.debug(u'get_image %s' % name) image = self._cache[name] if image.image is None: - self._clean_queue.remove((image.priority, image)) + self._conversion_queue.remove((image.priority, image)) image.priority = Priority.High - self._clean_queue.put((image.priority, image)) + self._conversion_queue.put((image.priority, image)) while image.image is None: log.debug(u'get_image - waiting') time.sleep(0.1) @@ -184,9 +184,9 @@ class ImageManager(QtCore.QObject): log.debug(u'get_image_bytes %s' % name) image = self._cache[name] if image.image_bytes is None: - self._clean_queue.remove((image.priority, image)) + self._conversion_queue.remove((image.priority, image)) image.priority = Priority.Urgent - self._clean_queue.put((image.priority, image)) + self._conversion_queue.put((image.priority, image)) while image.image_bytes is None: log.debug(u'get_image_bytes - waiting') time.sleep(0.1) @@ -198,6 +198,8 @@ class ImageManager(QtCore.QObject): """ log.debug(u'del_image %s' % name) if name in self._cache: + self._conversion_queue.remove( + (self._cache[name].priority, self._cache[name])) del self._cache[name] def add_image(self, name, path): @@ -208,7 +210,7 @@ class ImageManager(QtCore.QObject): if not name in self._cache: image = Image(name, path) self._cache[name] = image - self._clean_queue.put((image.priority, image)) + self._conversion_queue.put((image.priority, image)) else: log.debug(u'Image in cache %s:%s' % (name, path)) # We want only one thread. @@ -220,7 +222,7 @@ class ImageManager(QtCore.QObject): Controls the processing called from a ``QtCore.QThread``. """ log.debug(u'_process - started') - while not self._clean_queue.empty(): + while not self._conversion_queue.empty(): self._process_cache() log.debug(u'_process - ended') @@ -229,25 +231,25 @@ class ImageManager(QtCore.QObject): Actually does the work. """ log.debug(u'_process_cache') - image = self._clean_queue.get()[1] + image = self._conversion_queue.get()[1] # Generate the QImage for the image. if image.image is None: image.image = resize_image(image.path, self.width, self.height) # Set the priority to Lowest and stop here as we need to process # more important images first. if image.priority == Priority.Normal: - self._clean_queue.remove((image.priority, image)) + self._conversion_queue.remove((image.priority, image)) image.priority = Priority.Lowest - self._clean_queue.put((image.priority, image)) + self._conversion_queue.put((image.priority, image)) return # For image with high priority we set the priority to Low, as the # byte stream might be needed earlier the byte stream of image with # Normal priority. We stop here as we need to process more important # images first. elif image.priority == Priority.High: - self._clean_queue.remove((image.priority, image)) + self._conversion_queue.remove((image.priority, image)) image.priority = Priority.Low - self._clean_queue.put((image.priority, image)) + self._conversion_queue.put((image.priority, image)) return # Generate the byte stream for the image. if image.image_bytes is None: