forked from openlp/openlp
New service file format
This commit is contained in:
parent
d2881ad6e1
commit
937c6c3e81
@ -302,6 +302,21 @@ def md5_hash(salt=None, data=None):
|
||||
return hash_value
|
||||
|
||||
|
||||
def sha256_file_hash(filename):
|
||||
"""
|
||||
Returns the hashed output of sha256 on the file content using Python3 hashlib
|
||||
|
||||
:param filename: Name of the file to hash
|
||||
:returns: str
|
||||
"""
|
||||
log.debug('sha256_hash(filename="{filename}")'.format(filename=filename))
|
||||
hash_obj = hashlib.sha256()
|
||||
with open(filename, 'rb') as f:
|
||||
for chunk in iter(lambda: f.read(65536), b''):
|
||||
hash_obj.update(chunk)
|
||||
return hash_obj.hexdigest()
|
||||
|
||||
|
||||
def qmd5_hash(salt=None, data=None):
|
||||
"""
|
||||
Returns the hashed output of MD5Sum on salt, data
|
||||
|
@ -309,6 +309,7 @@ class DisplayWindow(QtWidgets.QWidget, RegistryProperties, LogMixin):
|
||||
imagesr = copy.deepcopy(images)
|
||||
for image in imagesr:
|
||||
image['path'] = image['path'].as_uri()
|
||||
image['thumbnail'] = image['thumbnail'].as_uri()
|
||||
json_images = json.dumps(imagesr)
|
||||
self.run_javascript('Display.setImageSlides({images});'.format(images=json_images))
|
||||
|
||||
|
@ -28,11 +28,12 @@ import os
|
||||
import uuid
|
||||
from copy import deepcopy
|
||||
from pathlib import Path
|
||||
from shutil import copytree, copy
|
||||
|
||||
from PyQt5 import QtGui
|
||||
|
||||
from openlp.core.state import State
|
||||
from openlp.core.common import ThemeLevel, md5_hash
|
||||
from openlp.core.common import ThemeLevel, sha256_file_hash
|
||||
from openlp.core.common.applocation import AppLocation
|
||||
from openlp.core.common.enum import ServiceItemType
|
||||
from openlp.core.common.i18n import translate
|
||||
@ -104,6 +105,8 @@ class ServiceItem(RegistryProperties):
|
||||
self.timed_slide_interval = 0
|
||||
self.will_auto_start = False
|
||||
self.has_original_files = True
|
||||
self.sha256_file_hash = None
|
||||
self.stored_filename = None
|
||||
self._new_item()
|
||||
self.metadata = []
|
||||
|
||||
@ -275,7 +278,7 @@ class ServiceItem(RegistryProperties):
|
||||
self._print_slides.append(slide)
|
||||
return self._print_slides
|
||||
|
||||
def add_from_image(self, path, title, background=None, thumbnail=None):
|
||||
def add_from_image(self, path, title, background=None, thumbnail=None, file_hash=None):
|
||||
"""
|
||||
Add an image slide to the service item.
|
||||
|
||||
@ -287,7 +290,9 @@ class ServiceItem(RegistryProperties):
|
||||
if background:
|
||||
self.image_border = background
|
||||
self.service_item_type = ServiceItemType.Image
|
||||
slide = {'title': title, 'path': path}
|
||||
if not file_hash:
|
||||
file_hash = sha256_file_hash(path)
|
||||
slide = {'title': title, 'path': path, 'file_hash': file_hash}
|
||||
if thumbnail:
|
||||
slide['thumbnail'] = thumbnail
|
||||
self.slides.append(slide)
|
||||
@ -311,7 +316,7 @@ class ServiceItem(RegistryProperties):
|
||||
self.slides.append({'title': title, 'text': text, 'verse': verse_tag})
|
||||
self._new_item()
|
||||
|
||||
def add_from_command(self, path, file_name, image, display_title=None, notes=None):
|
||||
def add_from_command(self, path, file_name, image, display_title=None, notes=None, file_hash=None):
|
||||
"""
|
||||
Add a slide from a command.
|
||||
|
||||
@ -320,18 +325,24 @@ class ServiceItem(RegistryProperties):
|
||||
:param image: The command of/for the slide.
|
||||
:param display_title: Title to show in gui/webinterface, optional.
|
||||
:param notes: Notes to show in the webinteface, optional.
|
||||
:param file_hash: Sha256 hash checksum of the file.
|
||||
"""
|
||||
self.service_item_type = ServiceItemType.Command
|
||||
# If the item should have a display title but this frame doesn't have one, we make one up
|
||||
if self.is_capable(ItemCapabilities.HasDisplayTitle) and not display_title:
|
||||
display_title = translate('OpenLP.ServiceItem',
|
||||
'[slide {frame:d}]').format(frame=len(self.slides) + 1)
|
||||
if self.uses_file():
|
||||
if file_hash:
|
||||
self.sha256_file_hash = file_hash
|
||||
else:
|
||||
file_location = Path(path) / file_name
|
||||
self.sha256_file_hash = sha256_file_hash(file_location)
|
||||
self.stored_filename = '{hash}{ext}'.format(hash=self.sha256_file_hash, ext=os.path.splitext(file_name)[1])
|
||||
# Update image path to match servicemanager location if file was loaded from service
|
||||
if image and not self.has_original_files and self.name == 'presentations':
|
||||
file_location = os.path.join(path, file_name)
|
||||
file_location_hash = md5_hash(file_location.encode('utf-8'))
|
||||
image = os.path.join(AppLocation.get_section_data_path(self.name), 'thumbnails', file_location_hash,
|
||||
ntpath.basename(image)) # TODO: Pathlib
|
||||
image = AppLocation.get_section_data_path(self.name) / 'thumbnails' / self.sha256_file_hash / \
|
||||
ntpath.basename(image)
|
||||
self.slides.append({'title': file_name, 'image': image, 'path': path, 'display_title': display_title,
|
||||
'notes': notes, 'thumbnail': image})
|
||||
# if self.is_capable(ItemCapabilities.HasThumbnails):
|
||||
@ -342,6 +353,10 @@ class ServiceItem(RegistryProperties):
|
||||
"""
|
||||
This method returns some text which can be saved into the service file to represent this item.
|
||||
"""
|
||||
if self.sha256_file_hash:
|
||||
stored_filename = '{hash}{ext}'.format(hash=self.sha256_file_hash, ext=os.path.splitext(self.title)[1])
|
||||
else:
|
||||
stored_filename = None
|
||||
service_header = {
|
||||
'name': self.name,
|
||||
'plugin': self.name,
|
||||
@ -366,7 +381,9 @@ class ServiceItem(RegistryProperties):
|
||||
'theme_overwritten': self.theme_overwritten,
|
||||
'will_auto_start': self.will_auto_start,
|
||||
'processor': self.processor,
|
||||
'metadata': self.metadata
|
||||
'metadata': self.metadata,
|
||||
'sha256_file_hash': self.sha256_file_hash,
|
||||
'stored_filename': stored_filename
|
||||
}
|
||||
service_data = []
|
||||
if self.service_item_type == ServiceItemType.Text:
|
||||
@ -378,15 +395,18 @@ class ServiceItem(RegistryProperties):
|
||||
elif self.service_item_type == ServiceItemType.Image:
|
||||
if lite_save:
|
||||
for slide in self.slides:
|
||||
service_data.append({'title': slide['title'], 'path': slide['path']})
|
||||
service_data.append({'title': slide['title'], 'path': slide['path'],
|
||||
'file_hash': slide['file_hash']})
|
||||
else:
|
||||
service_data = [slide['title'] for slide in self.slides]
|
||||
for slide in self.slides:
|
||||
image_path = slide['thumbnail'].relative_to(AppLocation().get_data_path())
|
||||
service_data.append({'title': slide['title'], 'image': image_path, 'file_hash': slide['file_hash']})
|
||||
elif self.service_item_type == ServiceItemType.Command:
|
||||
for slide in self.slides:
|
||||
if isinstance(slide['image'], QtGui.QIcon):
|
||||
image = "clapperboard"
|
||||
image = 'clapperboard'
|
||||
else:
|
||||
image = slide['image']
|
||||
image = slide['image'].relative_to(AppLocation().get_data_path())
|
||||
service_data.append({'title': slide['title'], 'image': image, 'path': slide['path'],
|
||||
'display_title': slide['display_title'], 'notes': slide['notes']})
|
||||
return {'header': service_header, 'data': service_data}
|
||||
@ -398,7 +418,7 @@ class ServiceItem(RegistryProperties):
|
||||
self._display_slides = []
|
||||
self._rendered_slides = []
|
||||
|
||||
def set_from_service(self, service_item, path=None):
|
||||
def set_from_service(self, service_item, path=None, version=2):
|
||||
"""
|
||||
This method takes a service item from a saved service file (passed from the ServiceManager) and extracts the
|
||||
data actually required.
|
||||
@ -406,6 +426,7 @@ class ServiceItem(RegistryProperties):
|
||||
:param service_item: The item to extract data from.
|
||||
:param path: Defaults to *None*. This is the service manager path for things which have their files saved
|
||||
with them or None when the saved service is lite and the original file paths need to be preserved.
|
||||
:param version: Format version of the data.
|
||||
"""
|
||||
log.debug('set_from_service called with path {path}'.format(path=path))
|
||||
header = service_item['serviceitem']['header']
|
||||
@ -433,11 +454,16 @@ class ServiceItem(RegistryProperties):
|
||||
self.processor = header.get('processor', None)
|
||||
self.has_original_files = True
|
||||
self.metadata = header.get('item_meta_data', [])
|
||||
self.sha256_file_hash = header.get('sha256_file_hash', None)
|
||||
self.stored_filename = header.get('stored_filename', self.title)
|
||||
if 'background_audio' in header and State().check_preconditions('media'):
|
||||
self.background_audio = []
|
||||
for file_path in header['background_audio']:
|
||||
# In OpenLP 3.0 we switched to storing Path objects in JSON files
|
||||
if isinstance(file_path, str):
|
||||
if version >= 3:
|
||||
if path:
|
||||
file_path = path / file_path
|
||||
else:
|
||||
# Handle service files prior to OpenLP 3.0
|
||||
# Windows can handle both forward and backward slashes, so we use ntpath to get the basename
|
||||
file_path = path / ntpath.basename(file_path)
|
||||
@ -453,26 +479,54 @@ class ServiceItem(RegistryProperties):
|
||||
if path:
|
||||
self.has_original_files = False
|
||||
for text_image in service_item['serviceitem']['data']:
|
||||
file_path = path / text_image
|
||||
self.add_from_image(file_path, text_image, background)
|
||||
file_hash = None
|
||||
thumbnail = None
|
||||
if version >= 3:
|
||||
text = text_image['title']
|
||||
file_hash = text_image['file_hash']
|
||||
file_path = path / '{base}{ext}'.format(base=file_hash, ext=os.path.splitext(text)[1])
|
||||
thumbnail = AppLocation.get_data_path() / text_image['image']
|
||||
# copy thumbnail for servicemanager path
|
||||
copy(path / 'thumbnails' / os.path.basename(text_image['image']),
|
||||
AppLocation.get_section_data_path(self.name) / 'thumbnails')
|
||||
else:
|
||||
text = text_image
|
||||
file_path = path / text
|
||||
self.add_from_image(file_path, text, background, thumbnail=thumbnail, file_hash=file_hash)
|
||||
else:
|
||||
for text_image in service_item['serviceitem']['data']:
|
||||
self.add_from_image(text_image['path'], text_image['title'], background)
|
||||
file_hash = None
|
||||
text = text_image['title']
|
||||
if version >= 3:
|
||||
file_hash = text_image['file_hash']
|
||||
self.add_from_image(text_image['path'], text, background, file_hash=file_hash)
|
||||
elif self.service_item_type == ServiceItemType.Command:
|
||||
for text_image in service_item['serviceitem']['data']:
|
||||
if not self.title:
|
||||
self.title = text_image['title']
|
||||
if self.is_capable(ItemCapabilities.IsOptical):
|
||||
if self.is_capable(ItemCapabilities.IsOptical) or self.is_capable(ItemCapabilities.CanStream):
|
||||
self.has_original_files = False
|
||||
self.add_from_command(text_image['path'], text_image['title'], text_image['image'])
|
||||
elif path:
|
||||
self.has_original_files = False
|
||||
if text_image['image'] == "clapperboard":
|
||||
# Copy any bundled thumbnails into the plugin thumbnail folder
|
||||
if version >= 3 and os.path.exists(path / self.sha256_file_hash) and \
|
||||
os.path.isdir(path / self.sha256_file_hash):
|
||||
try:
|
||||
copytree(path / self.sha256_file_hash,
|
||||
AppLocation.get_section_data_path(self.name) / 'thumbnails' /
|
||||
self.sha256_file_hash)
|
||||
except FileExistsError:
|
||||
# Files already exists, just skip
|
||||
pass
|
||||
if text_image['image'] == 'clapperboard':
|
||||
text_image['image'] = UiIcons().clapperboard
|
||||
self.add_from_command(path, text_image['title'], text_image['image'],
|
||||
text_image.get('display_title', ''), text_image.get('notes', ''))
|
||||
text_image.get('display_title', ''), text_image.get('notes', ''),
|
||||
file_hash=self.sha256_file_hash)
|
||||
else:
|
||||
self.add_from_command(Path(text_image['path']), text_image['title'], text_image['image'])
|
||||
self.add_from_command(Path(text_image['path']), text_image['title'], text_image['image'],
|
||||
file_hash=self.sha256_file_hash)
|
||||
self._new_item()
|
||||
|
||||
def get_display_title(self):
|
||||
@ -548,7 +602,8 @@ class ServiceItem(RegistryProperties):
|
||||
Confirms if the ServiceItem uses a file
|
||||
"""
|
||||
return self.service_item_type == ServiceItemType.Image or \
|
||||
(self.service_item_type == ServiceItemType.Command and not self.is_capable(ItemCapabilities.IsOptical))
|
||||
(self.service_item_type == ServiceItemType.Command and not self.is_capable(ItemCapabilities.IsOptical)
|
||||
and not self.is_capable(ItemCapabilities.CanStream))
|
||||
|
||||
def is_text(self):
|
||||
"""
|
||||
@ -608,6 +663,8 @@ class ServiceItem(RegistryProperties):
|
||||
return ''
|
||||
if self.is_image() or self.is_capable(ItemCapabilities.IsOptical):
|
||||
path_from = frame['path']
|
||||
elif self.is_command() and not self.has_original_files and self.sha256_file_hash:
|
||||
path_from = os.path.join(frame['path'], self.stored_filename)
|
||||
else:
|
||||
path_from = os.path.join(frame['path'], frame['title'])
|
||||
if isinstance(path_from, str):
|
||||
@ -698,8 +755,11 @@ class ServiceItem(RegistryProperties):
|
||||
self.is_valid = False
|
||||
break
|
||||
else:
|
||||
file_name = os.path.join(slide['path'], slide['title'])
|
||||
if not os.path.exists(file_name):
|
||||
if self.has_original_files:
|
||||
file_name = Path(slide['path']) / slide['title']
|
||||
else:
|
||||
file_name = Path(slide['path']) / self.stored_filename
|
||||
if not file_name.exists():
|
||||
self.is_valid = False
|
||||
break
|
||||
if suffixes and not self.is_text():
|
||||
@ -707,3 +767,14 @@ class ServiceItem(RegistryProperties):
|
||||
if file_suffix.lower() not in suffixes:
|
||||
self.is_valid = False
|
||||
break
|
||||
|
||||
def get_thumbnail_path(self):
|
||||
"""
|
||||
Returns the thumbnail folder. Should only be used for items that support thumbnails.
|
||||
"""
|
||||
if self.is_capable(ItemCapabilities.HasThumbnails):
|
||||
if self.is_command() and self.slides:
|
||||
return os.path.dirname(self.slides[0]['image'])
|
||||
elif self.is_image() and self.slides:
|
||||
return os.path.dirname(self.slides[0]['thumbnail'])
|
||||
return None
|
||||
|
@ -33,8 +33,8 @@ from tempfile import NamedTemporaryFile
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
from openlp.core.common import ThemeLevel, delete_file, sha256_file_hash
|
||||
from openlp.core.state import State
|
||||
from openlp.core.common import ThemeLevel, delete_file
|
||||
from openlp.core.common.actions import ActionList, CategoryOrder
|
||||
from openlp.core.common.applocation import AppLocation
|
||||
from openlp.core.common.enum import ServiceItemType
|
||||
@ -42,10 +42,10 @@ from openlp.core.common.i18n import UiStrings, format_time, translate
|
||||
from openlp.core.common.json import OpenLPJSONDecoder, OpenLPJSONEncoder
|
||||
from openlp.core.common.mixins import LogMixin, RegistryProperties
|
||||
from openlp.core.common.registry import Registry, RegistryBase
|
||||
from openlp.core.lib import build_icon
|
||||
from openlp.core.lib import build_icon, ItemCapabilities
|
||||
from openlp.core.lib.exceptions import ValidationError
|
||||
from openlp.core.lib.plugin import PluginStatus
|
||||
from openlp.core.lib.serviceitem import ItemCapabilities, ServiceItem
|
||||
from openlp.core.lib.serviceitem import ServiceItem
|
||||
from openlp.core.lib.ui import create_widget_action, critical_error_message_box, find_and_set_in_combo_box
|
||||
from openlp.core.ui.icons import UiIcons
|
||||
from openlp.core.ui.media import AUDIO_EXT, VIDEO_EXT
|
||||
@ -332,6 +332,7 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi
|
||||
self._service_path = None
|
||||
self.service_has_all_original_files = True
|
||||
self.list_double_clicked = False
|
||||
self.servicefile_version = None
|
||||
|
||||
def bootstrap_initialise(self):
|
||||
"""
|
||||
@ -518,9 +519,15 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi
|
||||
:return: service array
|
||||
"""
|
||||
service = []
|
||||
# Regarding openlp-servicefile-version:
|
||||
# 1: OpenLP 1? Not used.
|
||||
# 2: OpenLP 2 (default when loading a service file without openlp-servicefile-version)
|
||||
# 3: The new format introduced in OpenLP 3.0.
|
||||
# Note that the servicefile-version numbering is not expected to follow the OpenLP version numbering.
|
||||
core = {
|
||||
'lite-service': self._save_lite,
|
||||
'service-theme': self.service_theme
|
||||
'service-theme': self.service_theme,
|
||||
'openlp-servicefile-version': 3
|
||||
}
|
||||
service.append({'openlp_core': core})
|
||||
return service
|
||||
@ -530,24 +537,60 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi
|
||||
Get a list of files used in the service and files that are missing.
|
||||
|
||||
:return: A list of files used in the service that exist, and a list of files that don't.
|
||||
:rtype: (list[Path], list[Path])
|
||||
:rtype: (list[Path], list[str])
|
||||
"""
|
||||
write_list = []
|
||||
missing_list = []
|
||||
# Run through all items
|
||||
for item in self.service_items:
|
||||
# If the item has files, see if they exists
|
||||
if item['service_item'].uses_file():
|
||||
for frame in item['service_item'].get_frames():
|
||||
path_from = item['service_item'].get_frame_path(frame=frame)
|
||||
if path_from in write_list or path_from in missing_list:
|
||||
path_from_path = Path(path_from)
|
||||
if item['service_item'].stored_filename:
|
||||
sha256_file_name = item['service_item'].stored_filename
|
||||
else:
|
||||
sha256_file_name = sha256_file_hash(path_from_path) + os.path.splitext(path_from)[1]
|
||||
path_from_tuple = (path_from_path, sha256_file_name)
|
||||
if path_from_tuple in write_list or str(path_from_path) in missing_list:
|
||||
continue
|
||||
if not os.path.exists(path_from):
|
||||
missing_list.append(Path(path_from))
|
||||
missing_list.append(str(path_from_path))
|
||||
else:
|
||||
write_list.append(Path(path_from))
|
||||
write_list.append(path_from_tuple)
|
||||
# For items that has thumbnails, add them to the list
|
||||
if item['service_item'].is_capable(ItemCapabilities.HasThumbnails):
|
||||
thumbnail_path = item['service_item'].get_thumbnail_path()
|
||||
thumbnail_path_parent = Path(thumbnail_path).parent
|
||||
if item['service_item'].is_command():
|
||||
# Run through everything in the thumbnail folder and add pictures
|
||||
for filename in os.listdir(thumbnail_path):
|
||||
# Skip non-pictures
|
||||
if os.path.splitext(filename)[1] not in ['.png', '.jpg']:
|
||||
continue
|
||||
filename_path = Path(thumbnail_path) / Path(filename)
|
||||
# Create a thumbnail path in the zip/service file
|
||||
service_path = filename_path.relative_to(thumbnail_path_parent)
|
||||
write_list.append((filename_path, service_path))
|
||||
elif item['service_item'].is_image():
|
||||
# Find all image thumbnails and store them
|
||||
# All image thumbnails will be put in a folder named 'thumbnails'
|
||||
for frame in item['service_item'].get_frames():
|
||||
if 'thumbnail' in frame:
|
||||
filename_path = Path(thumbnail_path) / Path(frame['thumbnail'])
|
||||
# Create a thumbnail path in the zip/service file
|
||||
service_path = filename_path.relative_to(thumbnail_path_parent)
|
||||
path_from_tuple = (filename_path, service_path)
|
||||
if path_from_tuple in write_list:
|
||||
continue
|
||||
write_list.append(path_from_tuple)
|
||||
for audio_path in item['service_item'].background_audio:
|
||||
if audio_path in write_list:
|
||||
service_path = sha256_file_hash(audio_path) + os.path.splitext(audio_path)[1]
|
||||
audio_path_tuple = (audio_path, service_path)
|
||||
if audio_path_tuple in write_list:
|
||||
continue
|
||||
write_list.append(audio_path)
|
||||
write_list.append(audio_path_tuple)
|
||||
return write_list, missing_list
|
||||
|
||||
def save_file(self):
|
||||
@ -569,7 +612,6 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi
|
||||
|
||||
if not self._save_lite:
|
||||
write_list, missing_list = self.get_write_file_list()
|
||||
|
||||
if missing_list:
|
||||
self.application.set_normal_cursor()
|
||||
title = translate('OpenLP.ServiceManager', 'Service File(s) Missing')
|
||||
@ -596,8 +638,8 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi
|
||||
service_content = json.dumps(service, cls=OpenLPJSONEncoder)
|
||||
service_content_size = len(bytes(service_content, encoding='utf-8'))
|
||||
total_size = service_content_size
|
||||
for file_item in write_list:
|
||||
total_size += file_item.stat().st_size
|
||||
for local_file_item, zip_file_item in write_list:
|
||||
total_size += local_file_item.stat().st_size
|
||||
self.log_debug('ServiceManager.save_file - ZIP contents size is %i bytes' % total_size)
|
||||
self.main_window.display_progress_bar(total_size)
|
||||
try:
|
||||
@ -607,9 +649,9 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi
|
||||
zip_file.writestr('service_data.osj', service_content)
|
||||
self.main_window.increment_progress_bar(service_content_size)
|
||||
# Finally add all the listed media files.
|
||||
for write_path in write_list:
|
||||
zip_file.write(write_path, write_path)
|
||||
self.main_window.increment_progress_bar(write_path.stat().st_size)
|
||||
for local_file_item, zip_file_item in write_list:
|
||||
zip_file.write(str(local_file_item), str(zip_file_item))
|
||||
self.main_window.increment_progress_bar(local_file_item.stat().st_size)
|
||||
with suppress(FileNotFoundError):
|
||||
file_path.unlink()
|
||||
os.link(temp_file.name, file_path)
|
||||
@ -699,11 +741,30 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi
|
||||
service_data = None
|
||||
self.application.set_busy_cursor()
|
||||
try:
|
||||
with zipfile.ZipFile(file_path) as zip_file:
|
||||
# TODO: figure out a way to use the presentation thumbnails from the service file
|
||||
with zipfile.ZipFile(str(file_path)) as zip_file:
|
||||
compressed_size = 0
|
||||
for zip_info in zip_file.infolist():
|
||||
compressed_size += zip_info.compress_size
|
||||
self.main_window.display_progress_bar(compressed_size)
|
||||
# First find the osj-file to find out how to handle the file
|
||||
for zip_info in zip_file.infolist():
|
||||
# The json file has been called 'service_data.osj' since OpenLP 3.0
|
||||
if zip_info.filename == 'service_data.osj' or zip_info.filename.endswith('osj'):
|
||||
with zip_file.open(zip_info, 'r') as json_file:
|
||||
service_data = json_file.read()
|
||||
break
|
||||
if service_data:
|
||||
items = json.loads(service_data, cls=OpenLPJSONDecoder)
|
||||
else:
|
||||
raise ValidationError(msg='No service data found')
|
||||
# Extract the service file version
|
||||
for item in items:
|
||||
if 'openlp_core' in item:
|
||||
item = item['openlp_core']
|
||||
self.servicefile_version = item.get('openlp-servicefile-version', 2)
|
||||
break
|
||||
self.log_debug('Service format version: %{ver}'.format(ver=self.servicefile_version))
|
||||
for zip_info in zip_file.infolist():
|
||||
self.log_debug('Extract file: {name}'.format(name=zip_info.filename))
|
||||
# The json file has been called 'service_data.osj' since OpenLP 3.0
|
||||
@ -711,19 +772,19 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi
|
||||
with zip_file.open(zip_info, 'r') as json_file:
|
||||
service_data = json_file.read()
|
||||
else:
|
||||
zip_info.filename = os.path.basename(zip_info.filename)
|
||||
zip_file.extract(zip_info, self.service_path)
|
||||
# Service files from earlier versions than 3 expects that all files are extracted
|
||||
# into the root of the service folder.
|
||||
if self.servicefile_version and self.servicefile_version < 3:
|
||||
zip_info.filename = os.path.basename(zip_info.filename.replace('/', os.path.sep))
|
||||
zip_file.extract(zip_info, str(self.service_path))
|
||||
self.main_window.increment_progress_bar(zip_info.compress_size)
|
||||
if service_data:
|
||||
items = json.loads(service_data, cls=OpenLPJSONDecoder)
|
||||
# Handle the content
|
||||
self.new_file()
|
||||
self.process_service_items(items)
|
||||
self.set_file_name(file_path)
|
||||
self.main_window.add_recent_file(file_path)
|
||||
self.set_modified(False)
|
||||
self.settings.setValue('servicemanager/last file', file_path)
|
||||
else:
|
||||
raise ValidationError(msg='No service data found')
|
||||
except (NameError, OSError, ValidationError, zipfile.BadZipFile):
|
||||
self.application.set_normal_cursor()
|
||||
self.log_exception('Problem loading service file {name}'.format(name=file_path))
|
||||
@ -755,9 +816,9 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi
|
||||
self.service_theme = theme
|
||||
else:
|
||||
if self._save_lite:
|
||||
service_item.set_from_service(item)
|
||||
service_item.set_from_service(item, version=self.servicefile_version)
|
||||
else:
|
||||
service_item.set_from_service(item, self.service_path)
|
||||
service_item.set_from_service(item, self.service_path, self.servicefile_version)
|
||||
service_item.validate_item(self.suffixes)
|
||||
if service_item.is_capable(ItemCapabilities.OnLoadUpdate):
|
||||
new_item = Registry().get(service_item.name).service_load(service_item)
|
||||
@ -1277,11 +1338,12 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi
|
||||
"""
|
||||
Empties the service_path of temporary files on system exit.
|
||||
"""
|
||||
for file_path in self.service_path.iterdir():
|
||||
delete_file(file_path)
|
||||
audio_path = self.service_path / 'audio'
|
||||
if audio_path.exists():
|
||||
shutil.rmtree(audio_path, True)
|
||||
for file_name in os.listdir(self.service_path):
|
||||
file_path = Path(self.service_path, file_name)
|
||||
if os.path.isdir(file_path):
|
||||
shutil.rmtree(file_path, True)
|
||||
else:
|
||||
delete_file(file_path)
|
||||
|
||||
def on_theme_combo_box_selected(self, current_index):
|
||||
"""
|
||||
|
@ -64,6 +64,7 @@ def init_schema(url):
|
||||
* id
|
||||
* group_id
|
||||
* file_path
|
||||
* file_hash
|
||||
"""
|
||||
session, metadata = init_db(url)
|
||||
|
||||
@ -78,7 +79,8 @@ def init_schema(url):
|
||||
image_filenames_table = Table('image_filenames', metadata,
|
||||
Column('id', types.Integer(), primary_key=True),
|
||||
Column('group_id', types.Integer(), ForeignKey('image_groups.id'), default=None),
|
||||
Column('file_path', PathType(), nullable=False)
|
||||
Column('file_path', PathType(), nullable=False),
|
||||
Column('file_hash', types.Unicode(128), nullable=False)
|
||||
)
|
||||
|
||||
mapper(ImageGroups, image_groups_table)
|
||||
|
@ -24,7 +24,7 @@ from pathlib import Path
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
from openlp.core.common import delete_file, get_images_filter
|
||||
from openlp.core.common import delete_file, get_images_filter, sha256_file_hash
|
||||
from openlp.core.common.applocation import AppLocation
|
||||
from openlp.core.common.i18n import UiStrings, get_natural_key, translate
|
||||
from openlp.core.common.path import create_paths
|
||||
@ -345,7 +345,7 @@ class ImageMediaItem(MediaManagerItem):
|
||||
:rtype: Path
|
||||
"""
|
||||
ext = image.file_path.suffix.lower()
|
||||
return self.service_path / '{name:d}{ext}'.format(name=image.id, ext=ext)
|
||||
return self.service_path / '{name:s}{ext}'.format(name=image.file_hash, ext=ext)
|
||||
|
||||
def load_full_list(self, images, initial_load=False, open_group=None):
|
||||
"""
|
||||
@ -490,6 +490,7 @@ class ImageMediaItem(MediaManagerItem):
|
||||
image_file = ImageFilenames()
|
||||
image_file.group_id = group_id
|
||||
image_file.file_path = image_path
|
||||
image_file.file_hash = sha256_file_hash(image_path)
|
||||
self.manager.save_object(image_file)
|
||||
self.main_window.increment_progress_bar()
|
||||
if reload_list and image_paths:
|
||||
|
@ -25,16 +25,17 @@ import json
|
||||
import logging
|
||||
from pathlib import Path
|
||||
|
||||
from sqlalchemy import Column, Table
|
||||
from sqlalchemy import Column, Table, types
|
||||
|
||||
from openlp.core.common import sha256_file_hash
|
||||
from openlp.core.common.applocation import AppLocation
|
||||
from openlp.core.common.db import drop_columns
|
||||
from openlp.core.common.json import OpenLPJSONEncoder
|
||||
from openlp.core.common.json import OpenLPJSONEncoder, OpenLPJSONDecoder
|
||||
from openlp.core.lib.db import PathType, get_upgrade_op
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
__version__ = 2
|
||||
__version__ = 3
|
||||
|
||||
|
||||
def upgrade_1(session, metadata):
|
||||
@ -67,3 +68,21 @@ def upgrade_2(session, metadata):
|
||||
else:
|
||||
op.drop_constraint('image_filenames', 'foreignkey')
|
||||
op.drop_column('image_filenames', 'filenames')
|
||||
|
||||
|
||||
def upgrade_3(session, metadata):
|
||||
"""
|
||||
Version 3 upgrade - add sha256 hash
|
||||
"""
|
||||
log.debug('Starting upgrade_3 for adding sha256 hashes')
|
||||
old_table = Table('image_filenames', metadata, autoload=True)
|
||||
if 'file_hash' not in [col.name for col in old_table.c.values()]:
|
||||
op = get_upgrade_op(session)
|
||||
op.add_column('image_filenames', Column('file_hash', types.Unicode(128)))
|
||||
conn = op.get_bind()
|
||||
results = conn.execute('SELECT * FROM image_filenames')
|
||||
for row in results.fetchall():
|
||||
file_path = json.loads(row.file_path, cls=OpenLPJSONDecoder)
|
||||
hash = sha256_file_hash(file_path)
|
||||
sql = 'UPDATE image_filenames SET file_hash = \'{hash}\' WHERE id = {id}'.format(hash=hash, id=row.id)
|
||||
conn.execute(sql)
|
||||
|
@ -166,20 +166,20 @@ class MediaMediaItem(MediaManagerItem, RegistryProperties):
|
||||
'The optical disc {name} is no longer available.').format(name=name))
|
||||
return False
|
||||
service_item.processor = 'vlc'
|
||||
service_item.add_capability(ItemCapabilities.IsOptical)
|
||||
service_item.add_from_command(filename, name, self.clapperboard)
|
||||
service_item.title = clip_name
|
||||
# Set the length
|
||||
service_item.set_media_length(end - start)
|
||||
service_item.start_time = start
|
||||
service_item.end_time = end
|
||||
service_item.add_capability(ItemCapabilities.IsOptical)
|
||||
elif filename.startswith('devicestream:') or filename.startswith('networkstream:'):
|
||||
# Special handling if the filename is a devicestream
|
||||
(name, mrl, options) = parse_stream_path(filename)
|
||||
service_item.processor = 'vlc'
|
||||
service_item.add_capability(ItemCapabilities.CanStream)
|
||||
service_item.add_from_command(filename, name, self.clapperboard)
|
||||
service_item.title = name
|
||||
service_item.add_capability(ItemCapabilities.CanStream)
|
||||
else:
|
||||
if not os.path.exists(filename):
|
||||
if not remote:
|
||||
|
@ -19,9 +19,13 @@
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||
##########################################################################
|
||||
import logging
|
||||
import os
|
||||
import shutil
|
||||
|
||||
from PyQt5 import QtCore, QtWidgets
|
||||
from pathlib import Path
|
||||
|
||||
from openlp.core.common import sha256_file_hash
|
||||
from openlp.core.common.i18n import UiStrings, get_natural_key, translate
|
||||
from openlp.core.common.path import path_to_str
|
||||
from openlp.core.common.registry import Registry
|
||||
@ -260,6 +264,37 @@ class PresentationMediaItem(MediaManagerItem):
|
||||
doc.presentation_deleted()
|
||||
doc.close_presentation()
|
||||
|
||||
def update_thumbnail_scheme(self, file_path):
|
||||
"""
|
||||
Update the thumbnail folder naming scheme to the new sha256 based one.
|
||||
"""
|
||||
# TODO: Can be removed when the upgrade path to OpenLP 3.0 is no longer needed, also ensure code in
|
||||
# PresentationDocument.get_thumbnail_folder and PresentationDocument.get_temp_folder is removed
|
||||
for cidx in self.controllers:
|
||||
if not self.controllers[cidx].enabled():
|
||||
# skip presentation controllers that are not enabled
|
||||
continue
|
||||
file_ext = file_path.suffix[1:]
|
||||
if file_ext in self.controllers[cidx].supports or file_ext in self.controllers[cidx].also_supports:
|
||||
doc = self.controllers[cidx].add_document(file_path)
|
||||
# Check if the file actually exists
|
||||
if file_path.exists():
|
||||
thumb_path = doc.get_thumbnail_folder()
|
||||
hash = sha256_file_hash(file_path)
|
||||
# Rename the thumbnail folder so that it uses the sha256 naming scheme
|
||||
if thumb_path.exists():
|
||||
new_folder = Path(os.path.split(thumb_path)[0]) / hash
|
||||
log.info('Moved thumbnails from {md5} to {sha256}'.format(md5=str(thumb_path),
|
||||
sha256=str(new_folder)))
|
||||
shutil.move(thumb_path, new_folder)
|
||||
# Rename the data folder, if one exists
|
||||
old_folder = doc.get_temp_folder()
|
||||
if old_folder.exists():
|
||||
new_folder = Path(os.path.split(old_folder)[0]) / hash
|
||||
log.info('Moved data from {md5} to {sha256}'.format(md5=str(old_folder),
|
||||
sha256=str(new_folder)))
|
||||
shutil.move(old_folder, new_folder)
|
||||
|
||||
def generate_slide_data(self, service_item, *, item=None, remote=False, context=ServiceItemContext.Service,
|
||||
file_path=None, **kwargs):
|
||||
"""
|
||||
@ -310,12 +345,13 @@ class PresentationMediaItem(MediaManagerItem):
|
||||
image_path = doc.get_temp_folder() / 'mainslide{number:0>3d}.png'.format(number=i)
|
||||
thumbnail_path = doc.get_thumbnail_folder() / 'slide{number:d}.png'.format(number=i)
|
||||
while image_path.is_file():
|
||||
service_item.add_from_image(image_path, file_name, thumbnail=str(thumbnail_path))
|
||||
service_item.add_from_image(image_path, file_name, thumbnail=thumbnail_path)
|
||||
i += 1
|
||||
image_path = doc.get_temp_folder() / 'mainslide{number:0>3d}.png'.format(number=i)
|
||||
thumbnail_path = doc.get_thumbnail_folder() / 'slide{number:d}.png'.format(number=i)
|
||||
service_item.add_capability(ItemCapabilities.HasThumbnails)
|
||||
doc.close_presentation()
|
||||
service_item.validate_item()
|
||||
return True
|
||||
else:
|
||||
# File is no longer present
|
||||
@ -358,10 +394,12 @@ class PresentationMediaItem(MediaManagerItem):
|
||||
note = ''
|
||||
if notes and len(notes) >= i:
|
||||
note = notes[i - 1]
|
||||
service_item.add_from_command(str(path), file_name, str(thumbnail_path), title, note)
|
||||
service_item.add_from_command(str(path), file_name, thumbnail_path, title, note,
|
||||
doc.get_sha256_file_hash())
|
||||
i += 1
|
||||
thumbnail_path = doc.get_thumbnail_path(i, True)
|
||||
doc.close_presentation()
|
||||
service_item.validate_item()
|
||||
return True
|
||||
else:
|
||||
# File is no longer present
|
||||
|
@ -24,11 +24,12 @@ from pathlib import Path
|
||||
|
||||
from PyQt5 import QtCore
|
||||
|
||||
from openlp.core.common import md5_hash
|
||||
from openlp.core.common import md5_hash, sha256_file_hash
|
||||
from openlp.core.common.applocation import AppLocation
|
||||
from openlp.core.common.path import create_paths
|
||||
from openlp.core.common.registry import Registry
|
||||
from openlp.core.lib import create_thumb, validate_thumb
|
||||
from openlp.core.common.settings import Settings
|
||||
from openlp.core.lib import create_thumb
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
@ -96,6 +97,7 @@ class PresentationDocument(object):
|
||||
:rtype: None
|
||||
"""
|
||||
self.controller = controller
|
||||
self._sha256_file_hash = None
|
||||
self.settings = Registry().get('settings')
|
||||
self._setup(document_path)
|
||||
|
||||
@ -145,6 +147,12 @@ class PresentationDocument(object):
|
||||
# get_temp_folder and PresentationPluginapp_startup is removed
|
||||
if self.settings.value('presentations/thumbnail_scheme') == 'md5':
|
||||
folder = md5_hash(bytes(self.file_path))
|
||||
elif Settings().value('presentations/thumbnail_scheme') == 'sha256file':
|
||||
if self._sha256_file_hash:
|
||||
folder = self._sha256_file_hash
|
||||
else:
|
||||
self._sha256_file_hash = sha256_file_hash(self.file_path)
|
||||
folder = self._sha256_file_hash
|
||||
else:
|
||||
folder = self.file_path.name
|
||||
return Path(self.controller.thumbnail_folder, folder)
|
||||
@ -160,13 +168,20 @@ class PresentationDocument(object):
|
||||
# get_thumbnail_folder and PresentationPluginapp_startup is removed
|
||||
if self.settings.value('presentations/thumbnail_scheme') == 'md5':
|
||||
folder = md5_hash(bytes(self.file_path))
|
||||
elif Settings().value('presentations/thumbnail_scheme') == 'sha256file':
|
||||
if self._sha256_file_hash:
|
||||
folder = self._sha256_file_hash
|
||||
else:
|
||||
self._sha256_file_hash = sha256_file_hash(self.file_path)
|
||||
folder = self._sha256_file_hash
|
||||
else:
|
||||
folder = self.file_path.name
|
||||
return Path(self.controller.temp_folder, folder)
|
||||
|
||||
def check_thumbnails(self):
|
||||
"""
|
||||
Check that the last thumbnail image exists and is valid and are more recent than the powerpoint file.
|
||||
Check that the last thumbnail image exists and is valid. It is not checked if presentation file is newer than
|
||||
thumbnail since the path is based on the file hash, so if it exists it is by definition up to date.
|
||||
|
||||
:return: If the thumbnail is valid
|
||||
:rtype: bool
|
||||
@ -174,7 +189,7 @@ class PresentationDocument(object):
|
||||
last_image_path = self.get_thumbnail_path(self.get_slide_count(), True)
|
||||
if not (last_image_path and last_image_path.is_file()):
|
||||
return False
|
||||
return validate_thumb(Path(self.file_path), Path(last_image_path))
|
||||
return True
|
||||
|
||||
def close_presentation(self):
|
||||
"""
|
||||
@ -359,6 +374,17 @@ class PresentationDocument(object):
|
||||
notes_path = self.get_thumbnail_folder() / 'slideNotes{number:d}.txt'.format(number=slide_no)
|
||||
notes_path.write_text(note)
|
||||
|
||||
def get_sha256_file_hash(self):
|
||||
"""
|
||||
Returns the sha256 file hash for the file.
|
||||
|
||||
:return: The sha256 file hash
|
||||
:rtype: str
|
||||
"""
|
||||
if not self._sha256_file_hash:
|
||||
self._sha256_file_hash = sha256_file_hash(self.file_path)
|
||||
return self._sha256_file_hash
|
||||
|
||||
|
||||
class PresentationController(object):
|
||||
"""
|
||||
|
@ -137,7 +137,11 @@ class PresentationPlugin(Plugin):
|
||||
for path in presentation_paths:
|
||||
self.media_item.clean_up_thumbnails(path, clean_for_update=True)
|
||||
self.media_item.list_view.clear()
|
||||
self.settings.setValue('presentations/thumbnail_scheme', 'md5')
|
||||
# Update the thumbnail scheme if needed
|
||||
if self.settings.value('presentations/thumbnail_scheme') != 'sha256file':
|
||||
for path in presentation_paths:
|
||||
self.media_item.update_thumbnail_scheme(path)
|
||||
self.settings.setValue('presentations/thumbnail_scheme', 'sha256file')
|
||||
self.media_item.validate_and_load(presentation_paths)
|
||||
|
||||
@staticmethod
|
||||
|
@ -26,7 +26,7 @@ import pytest
|
||||
from pathlib import Path
|
||||
from unittest.mock import Mock, MagicMock, patch
|
||||
|
||||
from openlp.core.common import ThemeLevel, md5_hash
|
||||
from openlp.core.common import ThemeLevel
|
||||
from openlp.core.common.enum import ServiceItemType
|
||||
from openlp.core.common.registry import Registry
|
||||
from openlp.core.lib.formattingtags import FormattingTags
|
||||
@ -75,7 +75,7 @@ def service_item_env(state):
|
||||
Registry().register('image_manager', MagicMock())
|
||||
|
||||
|
||||
def test_service_item_basic():
|
||||
def test_service_item_basic(settings):
|
||||
"""
|
||||
Test the Service Item - basic test
|
||||
"""
|
||||
@ -123,7 +123,7 @@ def test_service_item_load_image_from_service(state_media, settings):
|
||||
# GIVEN: A new service item and a mocked add icon function
|
||||
image_name = 'image_1.jpg'
|
||||
test_file = TEST_PATH / image_name
|
||||
frame_array = {'path': test_file, 'title': image_name}
|
||||
frame_array = {'path': test_file, 'title': image_name, 'file_hash': 'abcd'}
|
||||
|
||||
service_item = ServiceItem(None)
|
||||
service_item.add_icon = MagicMock()
|
||||
@ -131,8 +131,9 @@ def test_service_item_load_image_from_service(state_media, settings):
|
||||
# WHEN: adding an image from a saved Service and mocked exists
|
||||
line = convert_file_service_item(TEST_PATH, 'serviceitem_image_1.osj')
|
||||
with patch('openlp.core.ui.servicemanager.os.path.exists') as mocked_exists,\
|
||||
patch('openlp.core.lib.serviceitem.AppLocation.get_section_data_path') as \
|
||||
mocked_get_section_data_path:
|
||||
patch('openlp.core.lib.serviceitem.AppLocation.get_section_data_path') as mocked_get_section_data_path,\
|
||||
patch('openlp.core.lib.serviceitem.sha256_file_hash') as mocked_sha256_file_hash:
|
||||
mocked_sha256_file_hash.return_value = 'abcd'
|
||||
mocked_exists.return_value = True
|
||||
mocked_get_section_data_path.return_value = Path('/path/')
|
||||
service_item.set_from_service(line, TEST_PATH)
|
||||
@ -169,8 +170,8 @@ def test_service_item_load_image_from_local_service(mocked_get_section_data_path
|
||||
image_name2 = 'image_2.jpg'
|
||||
test_file1 = os.path.join('/home', 'openlp', image_name1)
|
||||
test_file2 = os.path.join('/home', 'openlp', image_name2)
|
||||
frame_array1 = {'path': test_file1, 'title': image_name1}
|
||||
frame_array2 = {'path': test_file2, 'title': image_name2}
|
||||
frame_array1 = {'path': test_file1, 'title': image_name1, 'file_hash': 'abcd'}
|
||||
frame_array2 = {'path': test_file2, 'title': image_name2, 'file_hash': 'abcd'}
|
||||
service_item = ServiceItem(None)
|
||||
service_item.add_icon = MagicMock()
|
||||
service_item2 = ServiceItem(None)
|
||||
@ -179,8 +180,10 @@ def test_service_item_load_image_from_local_service(mocked_get_section_data_path
|
||||
# WHEN: adding an image from a saved Service and mocked exists
|
||||
line = convert_file_service_item(TEST_PATH, 'serviceitem_image_2.osj')
|
||||
line2 = convert_file_service_item(TEST_PATH, 'serviceitem_image_2.osj', 1)
|
||||
service_item2.set_from_service(line2)
|
||||
service_item.set_from_service(line)
|
||||
with patch('openlp.core.lib.serviceitem.sha256_file_hash') as mocked_sha256_file_hash:
|
||||
mocked_sha256_file_hash.return_value = 'abcd'
|
||||
service_item2.set_from_service(line2)
|
||||
service_item.set_from_service(line)
|
||||
|
||||
# THEN: We should get back a valid service item
|
||||
assert service_item.is_valid is True, 'The first service item should be valid'
|
||||
@ -226,7 +229,9 @@ def test_add_from_command_for_a_presentation():
|
||||
'display_title': display_title, 'notes': notes, 'thumbnail': image}
|
||||
|
||||
# WHEN: adding presentation to service_item
|
||||
service_item.add_from_command(TEST_PATH, presentation_name, image, display_title, notes)
|
||||
with patch('openlp.core.lib.serviceitem.sha256_file_hash') as mocked_sha256_file_hash:
|
||||
mocked_sha256_file_hash.return_value = 'abcd'
|
||||
service_item.add_from_command(TEST_PATH, presentation_name, image, display_title, notes)
|
||||
|
||||
# THEN: verify that it is setup as a Command and that the frame data matches
|
||||
assert service_item.service_item_type == ServiceItemType.Command, 'It should be a Command'
|
||||
@ -245,7 +250,9 @@ def test_add_from_command_without_display_title_and_notes():
|
||||
'display_title': None, 'notes': None, 'thumbnail': image}
|
||||
|
||||
# WHEN: adding image to service_item
|
||||
service_item.add_from_command(TEST_PATH, image_name, image)
|
||||
with patch('openlp.core.lib.serviceitem.sha256_file_hash') as mocked_sha256_file_hash:
|
||||
mocked_sha256_file_hash.return_value = 'abcd'
|
||||
service_item.add_from_command(TEST_PATH, image_name, image)
|
||||
|
||||
# THEN: verify that it is setup as a Command and that the frame data matches
|
||||
assert service_item.service_item_type == ServiceItemType.Command, 'It should be a Command'
|
||||
@ -268,13 +275,14 @@ def test_add_from_command_for_a_presentation_thumb(mocked_get_section_data_path,
|
||||
thumb = Path('tmp') / 'test' / 'thumb.png'
|
||||
display_title = 'DisplayTitle'
|
||||
notes = 'Note1\nNote2\n'
|
||||
expected_thumb_path = Path('mocked') / 'section' / 'path' / 'thumbnails' / \
|
||||
md5_hash(str(TEST_PATH / presentation_name).encode('utf8')) / 'thumb.png'
|
||||
frame = {'title': presentation_name, 'image': str(expected_thumb_path), 'path': str(TEST_PATH),
|
||||
'display_title': display_title, 'notes': notes, 'thumbnail': str(expected_thumb_path)}
|
||||
expected_thumb_path = Path('mocked') / 'section' / 'path' / 'thumbnails' / 'abcd' / 'thumb.png'
|
||||
frame = {'title': presentation_name, 'image': expected_thumb_path, 'path': str(TEST_PATH),
|
||||
'display_title': display_title, 'notes': notes, 'thumbnail': expected_thumb_path}
|
||||
|
||||
# WHEN: adding presentation to service_item
|
||||
service_item.add_from_command(str(TEST_PATH), presentation_name, thumb, display_title, notes)
|
||||
with patch('openlp.core.lib.serviceitem.sha256_file_hash') as mocked_sha256_file_hash:
|
||||
mocked_sha256_file_hash.return_value = 'abcd'
|
||||
service_item.add_from_command(str(TEST_PATH), presentation_name, thumb, display_title, notes)
|
||||
|
||||
# THEN: verify that it is setup as a Command and that the frame data matches
|
||||
assert service_item.service_item_type == ServiceItemType.Command, 'It should be a Command'
|
||||
@ -292,7 +300,9 @@ def test_service_item_load_optical_media_from_service(state_media):
|
||||
|
||||
# WHEN: We load a serviceitem with optical media
|
||||
line = convert_file_service_item(TEST_PATH, 'serviceitem-dvd.osj')
|
||||
with patch('openlp.core.ui.servicemanager.os.path.exists') as mocked_exists:
|
||||
with patch('openlp.core.ui.servicemanager.os.path.exists') as mocked_exists,\
|
||||
patch('openlp.core.lib.serviceitem.sha256_file_hash') as mocked_sha256_file_hash:
|
||||
mocked_sha256_file_hash.return_value = 'abcd'
|
||||
mocked_exists.return_value = True
|
||||
service_item.set_from_service(line)
|
||||
|
||||
|
@ -56,12 +56,15 @@ def _recursively_delete_group_side_effect(*args, **kwargs):
|
||||
returned_object1 = ImageFilenames()
|
||||
returned_object1.id = 1
|
||||
returned_object1.file_path = Path('/', 'tmp', 'test_file_1.jpg')
|
||||
returned_object1.file_hash = 'abcd1'
|
||||
returned_object2 = ImageFilenames()
|
||||
returned_object2.id = 2
|
||||
returned_object2.file_path = Path('/', 'tmp', 'test_file_2.jpg')
|
||||
returned_object2.file_hash = 'abcd2'
|
||||
returned_object3 = ImageFilenames()
|
||||
returned_object3.id = 3
|
||||
returned_object3.file_path = Path('/', 'tmp', 'test_file_3.jpg')
|
||||
returned_object3.file_hash = 'abcd3'
|
||||
return [returned_object1, returned_object2, returned_object3]
|
||||
if args[0] == ImageGroups and args[1]:
|
||||
# Change the parent_id that is matched so we don't get into an endless loop
|
||||
@ -91,7 +94,8 @@ def test_save_new_images_list_empty_list(mocked_load_full_list, media_item):
|
||||
|
||||
|
||||
@patch('openlp.plugins.images.lib.mediaitem.ImageMediaItem.load_full_list')
|
||||
def test_save_new_images_list_single_image_with_reload(mocked_load_full_list, media_item):
|
||||
@patch('openlp.plugins.images.lib.mediaitem.sha256_file_hash')
|
||||
def test_save_new_images_list_single_image_with_reload(mocked_sha256_file_hash, mocked_load_full_list, media_item):
|
||||
"""
|
||||
Test that the save_new_images_list() calls load_full_list() when reload_list is set to True
|
||||
"""
|
||||
@ -99,6 +103,7 @@ def test_save_new_images_list_single_image_with_reload(mocked_load_full_list, me
|
||||
image_list = [Path('test_image.jpg')]
|
||||
ImageFilenames.file_path = None
|
||||
media_item.manager = MagicMock()
|
||||
mocked_sha256_file_hash.return_value = 'abcd'
|
||||
|
||||
# WHEN: We run save_new_images_list with reload_list=True
|
||||
media_item.save_new_images_list(image_list, reload_list=True)
|
||||
@ -111,13 +116,15 @@ def test_save_new_images_list_single_image_with_reload(mocked_load_full_list, me
|
||||
|
||||
|
||||
@patch('openlp.plugins.images.lib.mediaitem.ImageMediaItem.load_full_list')
|
||||
def test_save_new_images_list_single_image_without_reload(mocked_load_full_list, media_item):
|
||||
@patch('openlp.plugins.images.lib.mediaitem.sha256_file_hash')
|
||||
def test_save_new_images_list_single_image_without_reload(mocked_sha256_file_hash, mocked_load_full_list, media_item):
|
||||
"""
|
||||
Test that the save_new_images_list() doesn't call load_full_list() when reload_list is set to False
|
||||
"""
|
||||
# GIVEN: A list with 1 image and a mocked out manager
|
||||
image_list = [Path('test_image.jpg')]
|
||||
media_item.manager = MagicMock()
|
||||
mocked_sha256_file_hash.return_value = 'abcd'
|
||||
|
||||
# WHEN: We run save_new_images_list with reload_list=False
|
||||
media_item.save_new_images_list(image_list, reload_list=False)
|
||||
@ -127,13 +134,15 @@ def test_save_new_images_list_single_image_without_reload(mocked_load_full_list,
|
||||
|
||||
|
||||
@patch('openlp.plugins.images.lib.mediaitem.ImageMediaItem.load_full_list')
|
||||
def test_save_new_images_list_multiple_images(mocked_load_full_list, media_item):
|
||||
@patch('openlp.plugins.images.lib.mediaitem.sha256_file_hash')
|
||||
def test_save_new_images_list_multiple_images(mocked_sha256_file_hash, mocked_load_full_list, media_item):
|
||||
"""
|
||||
Test that the save_new_images_list() saves all images in the list
|
||||
"""
|
||||
# GIVEN: A list with 3 images
|
||||
image_list = [Path('test_image_1.jpg'), Path('test_image_2.jpg'), Path('test_image_3.jpg')]
|
||||
media_item.manager = MagicMock()
|
||||
mocked_sha256_file_hash.return_value = 'abcd'
|
||||
|
||||
# WHEN: We run save_new_images_list with the list of 3 images
|
||||
media_item.save_new_images_list(image_list, reload_list=False)
|
||||
@ -144,13 +153,15 @@ def test_save_new_images_list_multiple_images(mocked_load_full_list, media_item)
|
||||
|
||||
|
||||
@patch('openlp.plugins.images.lib.mediaitem.ImageMediaItem.load_full_list')
|
||||
def test_save_new_images_list_other_objects_in_list(mocked_load_full_list, media_item):
|
||||
@patch('openlp.plugins.images.lib.mediaitem.sha256_file_hash')
|
||||
def test_save_new_images_list_other_objects_in_list(mocked_sha256_file_hash, mocked_load_full_list, media_item):
|
||||
"""
|
||||
Test that the save_new_images_list() ignores everything in the provided list except strings
|
||||
"""
|
||||
# GIVEN: A list with images and objects
|
||||
image_list = [Path('test_image_1.jpg'), None, True, ImageFilenames(), Path('test_image_2.jpg')]
|
||||
media_item.manager = MagicMock()
|
||||
mocked_sha256_file_hash.return_value = 'abcd'
|
||||
|
||||
# WHEN: We run save_new_images_list with the list of images and objects
|
||||
media_item.save_new_images_list(image_list, reload_list=False)
|
||||
@ -177,7 +188,8 @@ def test_on_reset_click(media_item):
|
||||
|
||||
|
||||
@patch('openlp.plugins.images.lib.mediaitem.delete_file')
|
||||
def test_recursively_delete_group(mocked_delete_file, media_item):
|
||||
@patch('openlp.core.lib.serviceitem.sha256_file_hash')
|
||||
def test_recursively_delete_group(mocked_sha256_file_hash, mocked_delete_file, media_item):
|
||||
"""
|
||||
Test that recursively_delete_group() works
|
||||
"""
|
||||
@ -189,6 +201,7 @@ def test_recursively_delete_group(mocked_delete_file, media_item):
|
||||
media_item.service_path = Path()
|
||||
test_group = ImageGroups()
|
||||
test_group.id = 1
|
||||
mocked_sha256_file_hash.return_value = 'abcd'
|
||||
|
||||
# WHEN: recursively_delete_group() is called
|
||||
media_item.recursively_delete_group(test_group)
|
||||
@ -205,7 +218,8 @@ def test_recursively_delete_group(mocked_delete_file, media_item):
|
||||
|
||||
@patch('openlp.plugins.images.lib.mediaitem.delete_file')
|
||||
@patch('openlp.plugins.images.lib.mediaitem.check_item_selected')
|
||||
def test_on_delete_click(mocked_check_item_selected, mocked_delete_file, media_item):
|
||||
@patch('openlp.core.lib.serviceitem.sha256_file_hash')
|
||||
def test_on_delete_click(mocked_sha256_file_hash, mocked_check_item_selected, mocked_delete_file, media_item):
|
||||
"""
|
||||
Test that on_delete_click() works
|
||||
"""
|
||||
@ -215,6 +229,7 @@ def test_on_delete_click(mocked_check_item_selected, mocked_delete_file, media_i
|
||||
test_image.id = 1
|
||||
test_image.group_id = 1
|
||||
test_image.file_path = Path('imagefile.png')
|
||||
test_image.file_hash = 'abcd'
|
||||
media_item.manager = MagicMock()
|
||||
media_item.service_path = Path()
|
||||
media_item.list_view = MagicMock()
|
||||
@ -222,6 +237,7 @@ def test_on_delete_click(mocked_check_item_selected, mocked_delete_file, media_i
|
||||
mocked_row_item.data.return_value = test_image
|
||||
mocked_row_item.text.return_value = ''
|
||||
media_item.list_view.selectedItems.return_value = [mocked_row_item]
|
||||
mocked_sha256_file_hash.return_value = 'abcd'
|
||||
|
||||
# WHEN: Calling on_delete_click
|
||||
media_item.on_delete_click()
|
||||
|
@ -57,11 +57,13 @@ def test_image_filenames_table(db_url, settings):
|
||||
Test that the ImageFilenames table is correctly upgraded to the latest version
|
||||
"""
|
||||
# GIVEN: An unversioned image database
|
||||
with patch.object(AppLocation, 'get_data_path', return_value=Path('/', 'test', 'dir')):
|
||||
with patch.object(AppLocation, 'get_data_path', return_value=Path('/', 'test', 'dir')),\
|
||||
patch('openlp.plugins.images.lib.upgrade.sha256_file_hash') as mocked_sha256_file_hash:
|
||||
mocked_sha256_file_hash.return_value = 'abcd'
|
||||
# WHEN: Initalising the database manager
|
||||
|
||||
upgrade_db(db_url, upgrade)
|
||||
|
||||
engine = create_engine(db_url)
|
||||
conn = engine.connect()
|
||||
assert conn.execute('SELECT * FROM metadata WHERE key = "version"').first().value == '2'
|
||||
assert conn.execute('SELECT * FROM metadata WHERE key = "version"').first().value == '3'
|
||||
|
@ -67,7 +67,7 @@ def test_replace_service_item(preview_widget, state_media):
|
||||
# GIVEN: A ServiceItem with two frames.
|
||||
service_item = ServiceItem(None)
|
||||
service = read_service_from_file('serviceitem_image_3.osj')
|
||||
with patch('os.path.exists'):
|
||||
with patch('os.path.exists') and patch('openlp.core.lib.serviceitem.sha256_file_hash'):
|
||||
service_item.set_from_service(service[0])
|
||||
# WHEN: Added to the preview widget.
|
||||
preview_widget.replace_service_item(service_item, 1, 1)
|
||||
@ -83,7 +83,7 @@ def test_change_slide(preview_widget, state_media):
|
||||
# GIVEN: A ServiceItem with two frames content.
|
||||
service_item = ServiceItem(None)
|
||||
service = read_service_from_file('serviceitem_image_3.osj')
|
||||
with patch('os.path.exists'):
|
||||
with patch('os.path.exists') and patch('openlp.core.lib.serviceitem.sha256_file_hash'):
|
||||
service_item.set_from_service(service[0])
|
||||
# WHEN: Added to the preview widget and switched to the second frame.
|
||||
preview_widget.replace_service_item(service_item, 1, 0)
|
||||
|
Loading…
Reference in New Issue
Block a user