- update the image cache when the image timestamp changes

- reworked image manager's _cache

bzr-revno: 2013
This commit is contained in:
Andreas Preikschat 2012-07-02 20:45:03 +02:00
commit 25177adce6
10 changed files with 126 additions and 88 deletions

View File

@ -36,6 +36,23 @@ from PyQt4 import QtCore, QtGui, Qt
log = logging.getLogger(__name__)
class ImageSource(object):
"""
This enumeration class represents different image sources. An image sources
states where an image is used. This enumeration class is need in the context
of the :class:~openlp.core.lib.imagemanager`.
``ImagePlugin``
This states that an image is being used by the image plugin.
``Theme``
This says, that the image is used by a theme.
"""
ImagePlugin = 1
Theme = 2
class MediaType(object):
"""
An enumeration class for types of media.

View File

@ -32,6 +32,7 @@ A Thread is used to convert the image to a byte array so the user does not need
to wait for the conversion to happen.
"""
import logging
import os
import time
import Queue
@ -97,19 +98,34 @@ 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.
This class represents an image. To mark an image as *dirty* call the
:class:`ImageManager`'s ``_resetImage`` method with the Image instance as
argument.
"""
secondary_priority = 0
def __init__(self, name, path, source, background):
self.name = name
def __init__(self, path, source, background):
"""
Create an image for the :class:`ImageManager`'s cache.
``path``
The image's file path. This should be an existing file path.
``source``
The source describes the image's origin. Possible values are
described in the :class:`~openlp.core.lib.ImageSource` class.
``background``
A ``QtGui.QColor`` object specifying the colour to be used to fill
the gabs if the image's ratio does not match with the display ratio.
"""
self.path = path
self.image = None
self.image_bytes = None
self.priority = Priority.Normal
self.source = source
self.background = background
self.timestamp = os.stat(path).st_mtime
self.secondary_priority = Image.secondary_priority
Image.secondary_priority += 1
@ -118,7 +134,7 @@ class PriorityQueue(Queue.PriorityQueue):
"""
Customised ``Queue.PriorityQueue``.
Each item in the queue must be tuple with three values. The first value
Each item in the queue must be a tuple with three values. The first value
is the :class:`Image`'s ``priority`` attribute, the second value
the :class:`Image`'s ``secondary_priority`` attribute. The last value the
:class:`Image` instance itself::
@ -187,7 +203,7 @@ class ImageManager(QtCore.QObject):
for image in self._cache.values():
self._resetImage(image)
def updateImages(self, imageType, background):
def updateImagesBorder(self, source, background):
"""
Border has changed so update all the images affected.
"""
@ -195,23 +211,27 @@ class ImageManager(QtCore.QObject):
# Mark the images as dirty for a rebuild by setting the image and byte
# stream to None.
for image in self._cache.values():
if image.source == imageType:
if image.source == source:
image.background = background
self._resetImage(image)
def updateImage(self, name, imageType, background):
def updateImageBorder(self, path, source, background):
"""
Border has changed so update the image affected.
"""
log.debug(u'updateImage')
# Mark the images as dirty for a rebuild by setting the image and byte
# Mark the image as dirty for a rebuild by setting the image and byte
# stream to None.
for image in self._cache.values():
if image.source == imageType and image.name == name:
image.background = background
self._resetImage(image)
image = self._cache[(path, source)]
if image.source == source:
image.background = background
self._resetImage(image)
def _resetImage(self, image):
"""
Mark the given :class:`Image` instance as dirt by setting its ``image``
and ``image_bytes`` attributes to None.
"""
image.image = None
image.image_bytes = None
self._conversionQueue.modify_priority(image, Priority.Normal)
@ -224,13 +244,13 @@ class ImageManager(QtCore.QObject):
if not self.imageThread.isRunning():
self.imageThread.start()
def getImage(self, name):
def getImage(self, path, source):
"""
Return the ``QImage`` from the cache. If not present wait for the
background thread to process it.
"""
log.debug(u'getImage %s' % name)
image = self._cache[name]
log.debug(u'getImage %s' % path)
image = self._cache[(path, source)]
if image.image is None:
self._conversionQueue.modify_priority(image, Priority.High)
# make sure we are running and if not give it a kick
@ -246,13 +266,13 @@ class ImageManager(QtCore.QObject):
self._conversionQueue.modify_priority(image, Priority.Low)
return image.image
def getImageBytes(self, name):
def getImageBytes(self, path, source):
"""
Returns the byte string for an image. If not present wait for the
background thread to process it.
"""
log.debug(u'getImageBytes %s' % name)
image = self._cache[name]
log.debug(u'getImageBytes %s' % path)
image = self._cache[(path, source)]
if image.image_bytes is None:
self._conversionQueue.modify_priority(image, Priority.Urgent)
# make sure we are running and if not give it a kick
@ -262,27 +282,22 @@ class ImageManager(QtCore.QObject):
time.sleep(0.1)
return image.image_bytes
def deleteImage(self, name):
"""
Delete the Image from the cache.
"""
log.debug(u'deleteImage %s' % name)
if name in self._cache:
self._conversionQueue.remove(self._cache[name])
del self._cache[name]
def addImage(self, name, path, source, background):
def addImage(self, path, source, background):
"""
Add image to cache if it is not already there.
"""
log.debug(u'addImage %s:%s' % (name, path))
if not name in self._cache:
image = Image(name, path, source, background)
self._cache[name] = image
log.debug(u'addImage %s' % path)
if not (path, source) in self._cache:
image = Image(path, source, background)
self._cache[(path, source)] = image
self._conversionQueue.put(
(image.priority, image.secondary_priority, image))
else:
log.debug(u'Image in cache %s:%s' % (name, path))
# Check if the there are any images with the same path and check if the
# timestamp has changed.
for image in self._cache.values():
if image.path == path and image.timestamp != os.stat(path).st_mtime:
image.timestamp = os.stat(path).st_mtime
self._resetImage(image)
# We want only one thread.
if not self.imageThread.isRunning():
self.imageThread.start()

View File

@ -32,7 +32,7 @@ from PyQt4 import QtGui, QtCore, QtWebKit
from openlp.core.lib import ServiceItem, expand_tags, \
build_lyrics_format_css, build_lyrics_outline_css, Receiver, \
ItemCapabilities, FormattingTags
ItemCapabilities, FormattingTags, ImageSource
from openlp.core.lib.theme import ThemeLevel
from openlp.core.ui import MainDisplay, ScreenList
@ -140,8 +140,8 @@ class Renderer(object):
self._theme_dimensions[theme_name]
# if No file do not update cache
if theme_data.background_filename:
self.image_manager.addImage(theme_data.theme_name,
theme_data.background_filename, u'theme',
self.image_manager.addImage(theme_data.background_filename,
ImageSource.Theme,
QtGui.QColor(theme_data.background_border_color))
def pre_render(self, override_theme_data=None):
@ -240,14 +240,13 @@ class Renderer(object):
# make big page for theme edit dialog to get line count
serviceItem.add_from_text(VERSE_FOR_LINE_COUNT)
else:
self.image_manager.deleteImage(theme_data.theme_name)
serviceItem.add_from_text(VERSE)
serviceItem.renderer = self
serviceItem.raw_footer = FOOTER
# if No file do not update cache
if theme_data.background_filename:
self.image_manager.addImage(theme_data.theme_name,
theme_data.background_filename, u'theme',
self.image_manager.addImage(theme_data.background_filename,
ImageSource.Theme,
QtGui.QColor(theme_data.background_border_color))
theme_data, main, footer = self.pre_render(theme_data)
serviceItem.themedata = theme_data

View File

@ -36,7 +36,8 @@ import logging
import os
import uuid
from openlp.core.lib import build_icon, clean_tags, expand_tags, translate
from openlp.core.lib import build_icon, clean_tags, expand_tags, translate, \
ImageSource
log = logging.getLogger(__name__)
@ -217,8 +218,8 @@ class ServiceItem(object):
self.image_border = background
self.service_item_type = ServiceItemType.Image
self._raw_frames.append({u'title': title, u'path': path})
self.renderer.image_manager.addImage(title, path, u'image',
self.image_border)
self.renderer.image_manager.addImage(
path, ImageSource.ImagePlugin, self.image_border)
self._new_item()
def add_from_text(self, raw_slide, verse_tag=None):
@ -432,13 +433,12 @@ class ServiceItem(object):
def get_rendered_frame(self, row):
"""
Returns the correct frame for a given list and
renders it if required.
Returns the correct frame for a given list and renders it if required.
"""
if self.service_item_type == ServiceItemType.Text:
return self._display_frames[row][u'html'].split(u'\n')[0]
elif self.service_item_type == ServiceItemType.Image:
return self._raw_frames[row][u'title']
return self._raw_frames[row][u'path']
else:
return self._raw_frames[row][u'image']

View File

@ -37,7 +37,7 @@ from PyQt4 import QtCore, QtGui, QtWebKit, QtOpenGL
from PyQt4.phonon import Phonon
from openlp.core.lib import Receiver, build_html, ServiceItem, image_to_byte, \
translate, PluginManager, expand_tags
translate, PluginManager, expand_tags, ImageSource
from openlp.core.lib.theme import BackgroundType
from openlp.core.lib.settings import Settings
@ -274,31 +274,33 @@ class MainDisplay(Display):
self.setVisible(False)
self.setGeometry(self.screen[u'size'])
def directImage(self, name, path, background):
def directImage(self, path, background):
"""
API for replacement backgrounds so Images are added directly to cache.
"""
self.imageManager.addImage(name, path, u'image', background)
if hasattr(self, u'serviceItem'):
self.override[u'image'] = name
self.override[u'theme'] = self.serviceItem.themedata.theme_name
self.image(name)
# Update the preview frame.
if self.isLive:
self.parent().updatePreview()
return True
return False
self.imageManager.addImage(path, ImageSource.ImagePlugin, background)
if not hasattr(self, u'serviceItem'):
return False
self.override[u'image'] = path
self.override[u'theme'] = self.serviceItem.themedata.background_filename
self.image(path)
# Update the preview frame.
if self.isLive:
self.parent().updatePreview()
return True
def image(self, name):
def image(self, path):
"""
Add an image as the background. The image has already been added to the
cache.
``name``
The name of the image to be displayed.
``path``
The path to the image to be displayed. **Note**, the path is only
passed to identify the image. If the image has changed it has to be
re-added to the image manager.
"""
log.debug(u'image to display')
image = self.imageManager.getImageBytes(name)
image = self.imageManager.getImageBytes(path, ImageSource.ImagePlugin)
self.controller.mediaController.video_reset(self.controller)
self.displayImage(image)
@ -360,7 +362,7 @@ class MainDisplay(Display):
self.setVisible(True)
return QtGui.QPixmap.grabWidget(self)
def buildHtml(self, serviceItem, image=None):
def buildHtml(self, serviceItem, image_path=u''):
"""
Store the serviceItem and build the new HTML from it. Add the
HTML to the display
@ -377,20 +379,23 @@ class MainDisplay(Display):
Receiver.send_message(u'video_background_replaced')
self.override = {}
# We have a different theme.
elif self.override[u'theme'] != serviceItem.themedata.theme_name:
elif self.override[u'theme'] != \
serviceItem.themedata.background_filename:
Receiver.send_message(u'live_theme_changed')
self.override = {}
else:
# replace the background
background = self.imageManager. \
getImageBytes(self.override[u'image'])
background = self.imageManager.getImageBytes(
self.override[u'image'], ImageSource.ImagePlugin)
self.setTransparency(self.serviceItem.themedata.background_type ==
BackgroundType.to_string(BackgroundType.Transparent))
if self.serviceItem.themedata.background_filename:
self.serviceItem.bg_image_bytes = self.imageManager. \
getImageBytes(self.serviceItem.themedata.theme_name)
if image:
image_bytes = self.imageManager.getImageBytes(image)
self.serviceItem.bg_image_bytes = self.imageManager.getImageBytes(
self.serviceItem.themedata.background_filename,
ImageSource.Theme)
if image_path:
image_bytes = self.imageManager.getImageBytes(
image_path, ImageSource.ImagePlugin)
else:
image_bytes = None
html = build_html(self.serviceItem, self.screen, self.isLive,

View File

@ -34,7 +34,7 @@ from collections import deque
from PyQt4 import QtCore, QtGui
from openlp.core.lib import OpenLPToolbar, Receiver, ItemCapabilities, \
translate, build_icon, build_html, PluginManager, ServiceItem
translate, build_icon, build_html, PluginManager, ServiceItem, ImageSource
from openlp.core.lib.ui import UiStrings, create_action
from openlp.core.lib.settings import Settings
from openlp.core.lib import SlideLimits, ServiceItemAction
@ -861,8 +861,10 @@ class SlideController(Controller):
# If current slide set background to image
if framenumber == slideno:
self.serviceItem.bg_image_bytes = \
self.imageManager.getImageBytes(frame[u'title'])
image = self.imageManager.getImage(frame[u'title'])
self.imageManager.getImageBytes(frame[u'path'],
ImageSource.ImagePlugin)
image = self.imageManager.getImage(frame[u'path'],
ImageSource.ImagePlugin)
label.setPixmap(QtGui.QPixmap.fromImage(image))
self.previewListWidget.setCellWidget(framenumber, 0, label)
slideHeight = width * (1 / self.ratio)
@ -1092,14 +1094,14 @@ class SlideController(Controller):
u'%s_slide' % self.serviceItem.name.lower(),
[self.serviceItem, self.isLive, row])
else:
toDisplay = self.serviceItem.get_rendered_frame(row)
to_display = self.serviceItem.get_rendered_frame(row)
if self.serviceItem.is_text():
self.display.text(toDisplay)
self.display.text(to_display)
else:
if start:
self.display.buildHtml(self.serviceItem, toDisplay)
self.display.buildHtml(self.serviceItem, to_display)
else:
self.display.image(toDisplay)
self.display.image(to_display)
# reset the store used to display first image
self.serviceItem.bg_image_bytes = None
self.updatePreview()

View File

@ -38,7 +38,7 @@ from PyQt4 import QtCore, QtGui
from openlp.core.lib import OpenLPToolbar, get_text_file_string, build_icon, \
Receiver, SettingsManager, translate, check_item_selected, \
check_directory_exists, create_thumb, validate_thumb
check_directory_exists, create_thumb, validate_thumb, ImageSource
from openlp.core.lib.theme import ThemeXML, BackgroundType, VerticalType, \
BackgroundGradientType
from openlp.core.lib.settings import Settings
@ -668,8 +668,9 @@ class ThemeManager(QtGui.QWidget):
self._writeTheme(theme, image_from, image_to)
if theme.background_type == \
BackgroundType.to_string(BackgroundType.Image):
self.mainwindow.imageManager.updateImage(theme.theme_name,
u'theme', QtGui.QColor(theme.background_border_color))
self.mainwindow.imageManager.updateImageBorder(
theme.background_filename,
ImageSource.Theme, QtGui.QColor(theme.background_border_color))
self.mainwindow.imageManager.processUpdates()
self.loadThemes()

View File

@ -31,7 +31,7 @@ from PyQt4 import QtCore, QtGui
import logging
from openlp.core.lib import Plugin, StringContent, build_icon, translate, \
Receiver
Receiver, ImageSource
from openlp.core.lib.settings import Settings
from openlp.plugins.images.lib import ImageMediaItem, ImageTab
@ -98,4 +98,5 @@ class ImagePlugin(Plugin):
"""
background = QtGui.QColor(Settings().value(self.settingsSection
+ u'/background color', QtCore.QVariant(u'#000000')))
self.liveController.imageManager.updateImages(u'image', background)
self.liveController.imageManager.updateImagesBorder(
ImageSource.ImagePlugin, background)

View File

@ -229,8 +229,7 @@ class ImageMediaItem(MediaManagerItem):
bitem = self.listView.item(item.row())
filename = unicode(bitem.data(QtCore.Qt.UserRole).toString())
if os.path.exists(filename):
name = os.path.split(filename)[1]
if self.plugin.liveController.display.directImage(name,
if self.plugin.liveController.display.directImage(
filename, background):
self.resetAction.setVisible(True)
else:

View File

@ -278,8 +278,7 @@ class MessageListener(object):
item = message[0]
log.debug(u'Startup called with message %s' % message)
hide_mode = message[2]
file = os.path.join(item.get_frame_path(),
item.get_frame_title())
file = os.path.join(item.get_frame_path(), item.get_frame_title())
self.handler = item.title
if self.handler == self.mediaitem.Automatic:
self.handler = self.mediaitem.findControllerByType(file)