- 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__) 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): class MediaType(object):
""" """
An enumeration class for types of media. 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. to wait for the conversion to happen.
""" """
import logging import logging
import os
import time import time
import Queue import Queue
@ -97,19 +98,34 @@ class Priority(object):
class Image(object): class Image(object):
""" """
This class represents an image. To mark an image as *dirty* set the instance This class represents an image. To mark an image as *dirty* call the
variables ``image`` and ``image_bytes`` to ``None`` and add the image object :class:`ImageManager`'s ``_resetImage`` method with the Image instance as
to the queue of images to process. argument.
""" """
secondary_priority = 0 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.path = path
self.image = None self.image = None
self.image_bytes = None self.image_bytes = None
self.priority = Priority.Normal self.priority = Priority.Normal
self.source = source self.source = source
self.background = background self.background = background
self.timestamp = os.stat(path).st_mtime
self.secondary_priority = Image.secondary_priority self.secondary_priority = Image.secondary_priority
Image.secondary_priority += 1 Image.secondary_priority += 1
@ -118,7 +134,7 @@ class PriorityQueue(Queue.PriorityQueue):
""" """
Customised ``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 is the :class:`Image`'s ``priority`` attribute, the second value
the :class:`Image`'s ``secondary_priority`` attribute. The last value the the :class:`Image`'s ``secondary_priority`` attribute. The last value the
:class:`Image` instance itself:: :class:`Image` instance itself::
@ -187,7 +203,7 @@ class ImageManager(QtCore.QObject):
for image in self._cache.values(): for image in self._cache.values():
self._resetImage(image) self._resetImage(image)
def updateImages(self, imageType, background): def updateImagesBorder(self, source, background):
""" """
Border has changed so update all the images affected. 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 # Mark the images as dirty for a rebuild by setting the image and byte
# stream to None. # stream to None.
for image in self._cache.values(): for image in self._cache.values():
if image.source == imageType: if image.source == source:
image.background = background image.background = background
self._resetImage(image) self._resetImage(image)
def updateImage(self, name, imageType, background): def updateImageBorder(self, path, source, background):
""" """
Border has changed so update the image affected. Border has changed so update the image affected.
""" """
log.debug(u'updateImage') 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. # stream to None.
for image in self._cache.values(): image = self._cache[(path, source)]
if image.source == imageType and image.name == name: if image.source == source:
image.background = background image.background = background
self._resetImage(image) self._resetImage(image)
def _resetImage(self, 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 = None
image.image_bytes = None image.image_bytes = None
self._conversionQueue.modify_priority(image, Priority.Normal) self._conversionQueue.modify_priority(image, Priority.Normal)
@ -224,13 +244,13 @@ class ImageManager(QtCore.QObject):
if not self.imageThread.isRunning(): if not self.imageThread.isRunning():
self.imageThread.start() self.imageThread.start()
def getImage(self, name): def getImage(self, path, source):
""" """
Return the ``QImage`` from the cache. If not present wait for the Return the ``QImage`` from the cache. If not present wait for the
background thread to process it. background thread to process it.
""" """
log.debug(u'getImage %s' % name) log.debug(u'getImage %s' % path)
image = self._cache[name] image = self._cache[(path, source)]
if image.image is None: if image.image is None:
self._conversionQueue.modify_priority(image, Priority.High) self._conversionQueue.modify_priority(image, Priority.High)
# make sure we are running and if not give it a kick # 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) self._conversionQueue.modify_priority(image, Priority.Low)
return image.image 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 Returns the byte string for an image. If not present wait for the
background thread to process it. background thread to process it.
""" """
log.debug(u'getImageBytes %s' % name) log.debug(u'getImageBytes %s' % path)
image = self._cache[name] image = self._cache[(path, source)]
if image.image_bytes is None: if image.image_bytes is None:
self._conversionQueue.modify_priority(image, Priority.Urgent) self._conversionQueue.modify_priority(image, Priority.Urgent)
# make sure we are running and if not give it a kick # make sure we are running and if not give it a kick
@ -262,27 +282,22 @@ class ImageManager(QtCore.QObject):
time.sleep(0.1) time.sleep(0.1)
return image.image_bytes return image.image_bytes
def deleteImage(self, name): def addImage(self, path, source, background):
"""
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):
""" """
Add image to cache if it is not already there. Add image to cache if it is not already there.
""" """
log.debug(u'addImage %s:%s' % (name, path)) log.debug(u'addImage %s' % path)
if not name in self._cache: if not (path, source) in self._cache:
image = Image(name, path, source, background) image = Image(path, source, background)
self._cache[name] = image self._cache[(path, source)] = image
self._conversionQueue.put( self._conversionQueue.put(
(image.priority, image.secondary_priority, image)) (image.priority, image.secondary_priority, image))
else: # Check if the there are any images with the same path and check if the
log.debug(u'Image in cache %s:%s' % (name, path)) # 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. # We want only one thread.
if not self.imageThread.isRunning(): if not self.imageThread.isRunning():
self.imageThread.start() self.imageThread.start()

View File

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

View File

@ -36,7 +36,8 @@ import logging
import os import os
import uuid 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__) log = logging.getLogger(__name__)
@ -217,8 +218,8 @@ class ServiceItem(object):
self.image_border = background self.image_border = background
self.service_item_type = ServiceItemType.Image self.service_item_type = ServiceItemType.Image
self._raw_frames.append({u'title': title, u'path': path}) self._raw_frames.append({u'title': title, u'path': path})
self.renderer.image_manager.addImage(title, path, u'image', self.renderer.image_manager.addImage(
self.image_border) path, ImageSource.ImagePlugin, self.image_border)
self._new_item() self._new_item()
def add_from_text(self, raw_slide, verse_tag=None): def add_from_text(self, raw_slide, verse_tag=None):
@ -432,13 +433,12 @@ class ServiceItem(object):
def get_rendered_frame(self, row): def get_rendered_frame(self, row):
""" """
Returns the correct frame for a given list and Returns the correct frame for a given list and renders it if required.
renders it if required.
""" """
if self.service_item_type == ServiceItemType.Text: if self.service_item_type == ServiceItemType.Text:
return self._display_frames[row][u'html'].split(u'\n')[0] return self._display_frames[row][u'html'].split(u'\n')[0]
elif self.service_item_type == ServiceItemType.Image: elif self.service_item_type == ServiceItemType.Image:
return self._raw_frames[row][u'title'] return self._raw_frames[row][u'path']
else: else:
return self._raw_frames[row][u'image'] 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 PyQt4.phonon import Phonon
from openlp.core.lib import Receiver, build_html, ServiceItem, image_to_byte, \ 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.theme import BackgroundType
from openlp.core.lib.settings import Settings from openlp.core.lib.settings import Settings
@ -274,31 +274,33 @@ class MainDisplay(Display):
self.setVisible(False) self.setVisible(False)
self.setGeometry(self.screen[u'size']) 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. API for replacement backgrounds so Images are added directly to cache.
""" """
self.imageManager.addImage(name, path, u'image', background) self.imageManager.addImage(path, ImageSource.ImagePlugin, background)
if hasattr(self, u'serviceItem'): if not hasattr(self, u'serviceItem'):
self.override[u'image'] = name return False
self.override[u'theme'] = self.serviceItem.themedata.theme_name self.override[u'image'] = path
self.image(name) self.override[u'theme'] = self.serviceItem.themedata.background_filename
# Update the preview frame. self.image(path)
if self.isLive: # Update the preview frame.
self.parent().updatePreview() if self.isLive:
return True self.parent().updatePreview()
return False return True
def image(self, name): def image(self, path):
""" """
Add an image as the background. The image has already been added to the Add an image as the background. The image has already been added to the
cache. cache.
``name`` ``path``
The name of the image to be displayed. 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') 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.controller.mediaController.video_reset(self.controller)
self.displayImage(image) self.displayImage(image)
@ -360,7 +362,7 @@ class MainDisplay(Display):
self.setVisible(True) self.setVisible(True)
return QtGui.QPixmap.grabWidget(self) 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 Store the serviceItem and build the new HTML from it. Add the
HTML to the display HTML to the display
@ -377,20 +379,23 @@ class MainDisplay(Display):
Receiver.send_message(u'video_background_replaced') Receiver.send_message(u'video_background_replaced')
self.override = {} self.override = {}
# We have a different theme. # 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') Receiver.send_message(u'live_theme_changed')
self.override = {} self.override = {}
else: else:
# replace the background # replace the background
background = self.imageManager. \ background = self.imageManager.getImageBytes(
getImageBytes(self.override[u'image']) self.override[u'image'], ImageSource.ImagePlugin)
self.setTransparency(self.serviceItem.themedata.background_type == self.setTransparency(self.serviceItem.themedata.background_type ==
BackgroundType.to_string(BackgroundType.Transparent)) BackgroundType.to_string(BackgroundType.Transparent))
if self.serviceItem.themedata.background_filename: if self.serviceItem.themedata.background_filename:
self.serviceItem.bg_image_bytes = self.imageManager. \ self.serviceItem.bg_image_bytes = self.imageManager.getImageBytes(
getImageBytes(self.serviceItem.themedata.theme_name) self.serviceItem.themedata.background_filename,
if image: ImageSource.Theme)
image_bytes = self.imageManager.getImageBytes(image) if image_path:
image_bytes = self.imageManager.getImageBytes(
image_path, ImageSource.ImagePlugin)
else: else:
image_bytes = None image_bytes = None
html = build_html(self.serviceItem, self.screen, self.isLive, 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 PyQt4 import QtCore, QtGui
from openlp.core.lib import OpenLPToolbar, Receiver, ItemCapabilities, \ 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.ui import UiStrings, create_action
from openlp.core.lib.settings import Settings from openlp.core.lib.settings import Settings
from openlp.core.lib import SlideLimits, ServiceItemAction from openlp.core.lib import SlideLimits, ServiceItemAction
@ -861,8 +861,10 @@ class SlideController(Controller):
# If current slide set background to image # If current slide set background to image
if framenumber == slideno: if framenumber == slideno:
self.serviceItem.bg_image_bytes = \ self.serviceItem.bg_image_bytes = \
self.imageManager.getImageBytes(frame[u'title']) self.imageManager.getImageBytes(frame[u'path'],
image = self.imageManager.getImage(frame[u'title']) ImageSource.ImagePlugin)
image = self.imageManager.getImage(frame[u'path'],
ImageSource.ImagePlugin)
label.setPixmap(QtGui.QPixmap.fromImage(image)) label.setPixmap(QtGui.QPixmap.fromImage(image))
self.previewListWidget.setCellWidget(framenumber, 0, label) self.previewListWidget.setCellWidget(framenumber, 0, label)
slideHeight = width * (1 / self.ratio) slideHeight = width * (1 / self.ratio)
@ -1092,14 +1094,14 @@ class SlideController(Controller):
u'%s_slide' % self.serviceItem.name.lower(), u'%s_slide' % self.serviceItem.name.lower(),
[self.serviceItem, self.isLive, row]) [self.serviceItem, self.isLive, row])
else: else:
toDisplay = self.serviceItem.get_rendered_frame(row) to_display = self.serviceItem.get_rendered_frame(row)
if self.serviceItem.is_text(): if self.serviceItem.is_text():
self.display.text(toDisplay) self.display.text(to_display)
else: else:
if start: if start:
self.display.buildHtml(self.serviceItem, toDisplay) self.display.buildHtml(self.serviceItem, to_display)
else: else:
self.display.image(toDisplay) self.display.image(to_display)
# reset the store used to display first image # reset the store used to display first image
self.serviceItem.bg_image_bytes = None self.serviceItem.bg_image_bytes = None
self.updatePreview() 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, \ from openlp.core.lib import OpenLPToolbar, get_text_file_string, build_icon, \
Receiver, SettingsManager, translate, check_item_selected, \ 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, \ from openlp.core.lib.theme import ThemeXML, BackgroundType, VerticalType, \
BackgroundGradientType BackgroundGradientType
from openlp.core.lib.settings import Settings from openlp.core.lib.settings import Settings
@ -668,8 +668,9 @@ class ThemeManager(QtGui.QWidget):
self._writeTheme(theme, image_from, image_to) self._writeTheme(theme, image_from, image_to)
if theme.background_type == \ if theme.background_type == \
BackgroundType.to_string(BackgroundType.Image): BackgroundType.to_string(BackgroundType.Image):
self.mainwindow.imageManager.updateImage(theme.theme_name, self.mainwindow.imageManager.updateImageBorder(
u'theme', QtGui.QColor(theme.background_border_color)) theme.background_filename,
ImageSource.Theme, QtGui.QColor(theme.background_border_color))
self.mainwindow.imageManager.processUpdates() self.mainwindow.imageManager.processUpdates()
self.loadThemes() self.loadThemes()

View File

@ -31,7 +31,7 @@ from PyQt4 import QtCore, QtGui
import logging import logging
from openlp.core.lib import Plugin, StringContent, build_icon, translate, \ from openlp.core.lib import Plugin, StringContent, build_icon, translate, \
Receiver Receiver, ImageSource
from openlp.core.lib.settings import Settings from openlp.core.lib.settings import Settings
from openlp.plugins.images.lib import ImageMediaItem, ImageTab from openlp.plugins.images.lib import ImageMediaItem, ImageTab
@ -98,4 +98,5 @@ class ImagePlugin(Plugin):
""" """
background = QtGui.QColor(Settings().value(self.settingsSection background = QtGui.QColor(Settings().value(self.settingsSection
+ u'/background color', QtCore.QVariant(u'#000000'))) + 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()) bitem = self.listView.item(item.row())
filename = unicode(bitem.data(QtCore.Qt.UserRole).toString()) filename = unicode(bitem.data(QtCore.Qt.UserRole).toString())
if os.path.exists(filename): if os.path.exists(filename):
name = os.path.split(filename)[1] if self.plugin.liveController.display.directImage(
if self.plugin.liveController.display.directImage(name,
filename, background): filename, background):
self.resetAction.setVisible(True) self.resetAction.setVisible(True)
else: else:

View File

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