mirror of https://gitlab.com/openlp/openlp.git
Merge branch 'migrate-images-to-folder-classes' into 'master'
Migrate Images plugin to use shared folder code See merge request openlp/openlp!593
This commit is contained in:
commit
1fc1bd4124
|
@ -21,16 +21,17 @@
|
|||
|
||||
import logging
|
||||
|
||||
from openlp.core.state import State
|
||||
from openlp.core.common.i18n import translate
|
||||
from openlp.core.lib import build_icon
|
||||
from openlp.core.lib.db import Manager
|
||||
from openlp.core.lib.plugin import Plugin, StringContent
|
||||
from openlp.core.state import State
|
||||
from openlp.core.ui.icons import UiIcons
|
||||
|
||||
from openlp.plugins.images.lib import upgrade
|
||||
from openlp.plugins.images.lib.mediaitem import ImageMediaItem
|
||||
from openlp.plugins.images.lib.imagetab import ImageTab
|
||||
from openlp.plugins.images.lib.db import init_schema
|
||||
from openlp.plugins.images.lib.imagetab import ImageTab
|
||||
from openlp.plugins.images.lib.mediaitem import ImageMediaItem
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
|
|
@ -21,9 +21,8 @@
|
|||
"""
|
||||
The :mod:`db` module provides the database and schema that is the backend for the Images plugin.
|
||||
"""
|
||||
from sqlalchemy import Column, ForeignKey, MetaData
|
||||
from sqlalchemy import MetaData
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy.types import Integer, Unicode
|
||||
|
||||
# Maintain backwards compatibility with older versions of SQLAlchemy while supporting SQLAlchemy 1.4+
|
||||
try:
|
||||
|
@ -31,31 +30,22 @@ try:
|
|||
except ImportError:
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
|
||||
from openlp.core.lib.db import PathType, init_db
|
||||
from openlp.core.lib.db import FolderMixin, ItemMixin, init_db
|
||||
|
||||
|
||||
Base = declarative_base(MetaData())
|
||||
|
||||
|
||||
class ImageGroups(Base):
|
||||
class Folder(Base, FolderMixin):
|
||||
"""
|
||||
ImageGroups model.
|
||||
Folder model.
|
||||
"""
|
||||
__tablename__ = 'image_groups'
|
||||
id = Column(Integer(), primary_key=True)
|
||||
parent_id = Column(Integer())
|
||||
group_name = Column(Unicode(128))
|
||||
|
||||
|
||||
class ImageFilenames(Base):
|
||||
class Item(Base, ItemMixin):
|
||||
"""
|
||||
ImageFilenames model.
|
||||
Item model.
|
||||
"""
|
||||
__tablename__ = 'image_filenames'
|
||||
id = Column(Integer(), primary_key=True)
|
||||
group_id = Column(Integer(), ForeignKey('image_groups.id'), default=None)
|
||||
file_path = Column(PathType(), nullable=False)
|
||||
file_hash = Column(Unicode(128), nullable=False)
|
||||
|
||||
|
||||
def init_schema(url: str) -> Session:
|
||||
|
|
|
@ -24,28 +24,25 @@ from pathlib import Path
|
|||
|
||||
from PyQt5 import QtCore, QtWidgets
|
||||
|
||||
from openlp.core.common import delete_file, get_images_filter, sha256_file_hash
|
||||
from openlp.core.common import delete_file, get_images_filter
|
||||
from openlp.core.common.applocation import AppLocation
|
||||
from openlp.core.common.i18n import UiStrings, get_natural_key, translate
|
||||
from openlp.core.common.enum import ImageThemeMode
|
||||
from openlp.core.common.i18n import UiStrings, translate
|
||||
from openlp.core.common.path import create_paths
|
||||
from openlp.core.common.registry import Registry
|
||||
from openlp.core.common.enum import ImageThemeMode
|
||||
from openlp.core.lib import ServiceItemContext, build_icon, check_item_selected, create_thumb, validate_thumb
|
||||
from openlp.core.lib.mediamanageritem import MediaManagerItem
|
||||
from openlp.core.lib.plugin import StringContent
|
||||
from openlp.core.lib.serviceitem import ItemCapabilities
|
||||
from openlp.core.lib.ui import create_widget_action, critical_error_message_box
|
||||
from openlp.core.ui.icons import UiIcons
|
||||
from openlp.core.widgets.views import TreeWidgetWithDnD
|
||||
from openlp.plugins.images.forms.addgroupform import AddGroupForm
|
||||
from openlp.plugins.images.forms.choosegroupform import ChooseGroupForm
|
||||
from openlp.plugins.images.lib.db import ImageFilenames, ImageGroups
|
||||
from openlp.core.ui.library import FolderLibraryItem
|
||||
|
||||
from openlp.plugins.images.lib.db import Folder, Item
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ImageMediaItem(MediaManagerItem):
|
||||
class ImageMediaItem(FolderLibraryItem):
|
||||
"""
|
||||
This is the custom media manager item for images.
|
||||
"""
|
||||
|
@ -56,9 +53,7 @@ class ImageMediaItem(MediaManagerItem):
|
|||
def __init__(self, parent, plugin):
|
||||
self.icon_path = 'images/image'
|
||||
self.manager = None
|
||||
self.choose_group_form = None
|
||||
self.add_group_form = None
|
||||
super(ImageMediaItem, self).__init__(parent, plugin)
|
||||
super(ImageMediaItem, self).__init__(parent, plugin, Folder, Item)
|
||||
|
||||
def setup_item(self):
|
||||
"""
|
||||
|
@ -68,22 +63,17 @@ class ImageMediaItem(MediaManagerItem):
|
|||
self.images_add_to_service.connect(self.add_to_service_remote)
|
||||
self.quick_preview_allowed = True
|
||||
self.has_search = True
|
||||
self.manager = self.plugin.manager
|
||||
self.choose_group_form = ChooseGroupForm(self)
|
||||
self.add_group_form = AddGroupForm(self)
|
||||
self.fill_groups_combobox(self.choose_group_form.group_combobox)
|
||||
self.fill_groups_combobox(self.add_group_form.parent_group_combobox)
|
||||
self.single_service_item = False
|
||||
Registry().register_function('live_theme_changed', self.on_display_changed)
|
||||
Registry().register_function('slidecontroller_live_started', self.on_display_changed)
|
||||
# Allow DnD from the desktop.
|
||||
self.list_view.activateDnD()
|
||||
|
||||
def retranslate_ui(self):
|
||||
super().retranslate_ui()
|
||||
self.on_new_prompt = translate('ImagePlugin.MediaItem', 'Select Image(s)')
|
||||
file_formats = get_images_filter()
|
||||
self.on_new_file_masks = '{formats};;{files} (*)'.format(formats=file_formats, files=UiStrings().AllFiles)
|
||||
self.add_group_action.setText(UiStrings().AddGroupDot)
|
||||
self.add_group_action.setToolTip(UiStrings().AddGroupDot)
|
||||
self.replace_action.setText(UiStrings().ReplaceBG)
|
||||
self.replace_action.setToolTip(UiStrings().ReplaceLiveBG)
|
||||
self.replace_action_context.setText(UiStrings().ReplaceBG)
|
||||
|
@ -112,101 +102,23 @@ class ImageMediaItem(MediaManagerItem):
|
|||
self.service_path = AppLocation.get_section_data_path(self.settings_section) / 'thumbnails'
|
||||
create_paths(self.service_path)
|
||||
# Load images from the database
|
||||
self.load_full_list(
|
||||
self.manager.get_all_objects(ImageFilenames, order_by_ref=ImageFilenames.file_path), initial_load=True)
|
||||
|
||||
def add_list_view_to_toolbar(self):
|
||||
"""
|
||||
Creates the main widget for listing items the media item is tracking. This method overloads
|
||||
MediaManagerItem.add_list_view_to_toolbar.
|
||||
"""
|
||||
# Add the List widget
|
||||
self.list_view = TreeWidgetWithDnD(self, self.plugin.name)
|
||||
self.list_view.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
|
||||
self.list_view.setAlternatingRowColors(True)
|
||||
self.list_view.setObjectName('{name}TreeView'.format(name=self.plugin.name))
|
||||
# Add to pageLayout
|
||||
self.page_layout.addWidget(self.list_view)
|
||||
# define and add the context menu
|
||||
self.list_view.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
|
||||
if self.has_edit_icon:
|
||||
create_widget_action(
|
||||
self.list_view,
|
||||
text=self.plugin.get_string(StringContent.Edit)['title'],
|
||||
icon=UiIcons().edit,
|
||||
triggers=self.on_edit_click)
|
||||
create_widget_action(self.list_view, separator=True)
|
||||
create_widget_action(
|
||||
self.list_view,
|
||||
'listView{name}{preview}Item'.format(name=self.plugin.name.title(), preview=StringContent.Preview.title()),
|
||||
text=self.plugin.get_string(StringContent.Preview)['title'],
|
||||
icon=UiIcons().preview,
|
||||
can_shortcuts=True,
|
||||
triggers=self.on_preview_click)
|
||||
create_widget_action(
|
||||
self.list_view,
|
||||
'listView{name}{live}Item'.format(name=self.plugin.name.title(), live=StringContent.Live.title()),
|
||||
text=self.plugin.get_string(StringContent.Live)['title'],
|
||||
icon=UiIcons().live,
|
||||
can_shortcuts=True,
|
||||
triggers=self.on_live_click)
|
||||
create_widget_action(
|
||||
self.list_view,
|
||||
'listView{name}{service}Item'.format(name=self.plugin.name.title(), service=StringContent.Service.title()),
|
||||
can_shortcuts=True,
|
||||
text=self.plugin.get_string(StringContent.Service)['title'],
|
||||
icon=UiIcons().add,
|
||||
triggers=self.on_add_click)
|
||||
if self.add_to_service_item:
|
||||
create_widget_action(self.list_view, separator=True)
|
||||
create_widget_action(
|
||||
self.list_view,
|
||||
text=translate('OpenLP.MediaManagerItem', '&Add to selected Service Item'),
|
||||
icon=UiIcons().add,
|
||||
triggers=self.on_add_edit_click)
|
||||
create_widget_action(self.list_view, separator=True)
|
||||
if self.has_delete_icon:
|
||||
create_widget_action(
|
||||
self.list_view,
|
||||
'listView{name}{delete}Item'.format(name=self.plugin.name.title(), delete=StringContent.Delete.title()),
|
||||
text=self.plugin.get_string(StringContent.Delete)['title'],
|
||||
icon=UiIcons().delete,
|
||||
can_shortcuts=True, triggers=self.on_delete_click)
|
||||
self.add_custom_context_actions()
|
||||
# Create the context menu and add all actions from the list_view.
|
||||
self.menu = QtWidgets.QMenu()
|
||||
self.menu.addActions(self.list_view.actions())
|
||||
self.list_view.doubleClicked.connect(self.on_double_clicked)
|
||||
self.list_view.itemSelectionChanged.connect(self.on_selection_change)
|
||||
self.list_view.customContextMenuRequested.connect(self.context_menu)
|
||||
self.list_view.addAction(self.replace_action)
|
||||
self.load_list(self.manager.get_all_objects(Item, order_by_ref=Item.file_path), is_initial_load=True)
|
||||
|
||||
def add_custom_context_actions(self):
|
||||
"""
|
||||
Add custom actions to the context menu.
|
||||
"""
|
||||
create_widget_action(self.list_view, separator=True)
|
||||
create_widget_action(
|
||||
self.list_view,
|
||||
text=UiStrings().AddGroup, icon=UiIcons().folder, triggers=self.on_add_group_click)
|
||||
create_widget_action(
|
||||
self.list_view,
|
||||
text=translate('ImagePlugin', 'Add new image(s)'),
|
||||
icon=UiIcons().open, triggers=self.on_file_click)
|
||||
create_widget_action(self.list_view, text=UiStrings().AddFolder, icon=UiIcons().folder,
|
||||
triggers=self.on_add_folder_click)
|
||||
create_widget_action(self.list_view, text=translate('ImagePlugin', 'Add new image(s)'),
|
||||
icon=UiIcons().open, triggers=self.on_file_click)
|
||||
create_widget_action(self.list_view, separator=True)
|
||||
self.replace_action_context = create_widget_action(
|
||||
self.list_view, text=UiStrings().ReplaceBG, icon=UiIcons().theme, triggers=self.on_replace_click)
|
||||
self.reset_action_context = create_widget_action(
|
||||
self.list_view, text=UiStrings().ReplaceLiveBG, icon=UiIcons().close,
|
||||
visible=False, triggers=self.on_reset_click)
|
||||
|
||||
def add_middle_header_bar(self):
|
||||
"""
|
||||
Add custom buttons to the start of the toolbar.
|
||||
"""
|
||||
self.add_group_action = self.toolbar.add_toolbar_action('add_group_action',
|
||||
icon=UiIcons().folder,
|
||||
triggers=self.on_add_group_click)
|
||||
self.replace_action_context = create_widget_action(self.list_view, text=UiStrings().ReplaceBG,
|
||||
icon=UiIcons().theme, triggers=self.on_replace_click)
|
||||
self.reset_action_context = create_widget_action(self.list_view, text=UiStrings().ReplaceLiveBG,
|
||||
icon=UiIcons().close, visible=False,
|
||||
triggers=self.on_reset_click)
|
||||
|
||||
def add_end_header_bar(self):
|
||||
"""
|
||||
|
@ -219,329 +131,49 @@ class ImageMediaItem(MediaManagerItem):
|
|||
icon=UiIcons().close,
|
||||
visible=False, triggers=self.on_reset_click)
|
||||
|
||||
def recursively_delete_group(self, image_group):
|
||||
"""
|
||||
Recursively deletes a group and all groups and images in it.
|
||||
|
||||
:param image_group: The ImageGroups instance of the group that will be deleted.
|
||||
"""
|
||||
images = self.manager.get_all_objects(ImageFilenames, ImageFilenames.group_id == image_group.id)
|
||||
for image in images:
|
||||
delete_file(self.service_path / image.file_path.name)
|
||||
delete_file(self.generate_thumbnail_path(image))
|
||||
self.manager.delete_object(ImageFilenames, image.id)
|
||||
image_groups = self.manager.get_all_objects(ImageGroups, ImageGroups.parent_id == image_group.id)
|
||||
for group in image_groups:
|
||||
self.recursively_delete_group(group)
|
||||
self.manager.delete_object(ImageGroups, group.id)
|
||||
|
||||
def on_delete_click(self):
|
||||
"""
|
||||
Remove an image item from the list.
|
||||
"""
|
||||
# Turn off auto preview triggers.
|
||||
self.list_view.blockSignals(True)
|
||||
if check_item_selected(self.list_view, translate('ImagePlugin.MediaItem',
|
||||
'You must select an image or group to delete.')):
|
||||
item_list = self.list_view.selectedItems()
|
||||
self.application.set_busy_cursor()
|
||||
self.main_window.display_progress_bar(len(item_list))
|
||||
for row_item in item_list:
|
||||
if row_item:
|
||||
item_data = row_item.data(0, QtCore.Qt.UserRole)
|
||||
if isinstance(item_data, ImageFilenames):
|
||||
delete_file(self.service_path / row_item.text(0))
|
||||
delete_file(self.generate_thumbnail_path(item_data))
|
||||
if item_data.group_id == 0:
|
||||
self.list_view.takeTopLevelItem(self.list_view.indexOfTopLevelItem(row_item))
|
||||
else:
|
||||
row_item.parent().removeChild(row_item)
|
||||
self.manager.delete_object(ImageFilenames, row_item.data(0, QtCore.Qt.UserRole).id)
|
||||
elif isinstance(item_data, ImageGroups):
|
||||
if QtWidgets.QMessageBox.question(
|
||||
self.list_view.parent(),
|
||||
translate('ImagePlugin.MediaItem', 'Remove group'),
|
||||
translate('ImagePlugin.MediaItem',
|
||||
'Are you sure you want to remove "{name}" and everything in it?'
|
||||
).format(name=item_data.group_name)
|
||||
) == QtWidgets.QMessageBox.Yes:
|
||||
self.recursively_delete_group(item_data)
|
||||
self.manager.delete_object(ImageGroups, row_item.data(0, QtCore.Qt.UserRole).id)
|
||||
if item_data.parent_id == 0:
|
||||
self.list_view.takeTopLevelItem(self.list_view.indexOfTopLevelItem(row_item))
|
||||
else:
|
||||
row_item.parent().removeChild(row_item)
|
||||
self.fill_groups_combobox(self.choose_group_form.group_combobox)
|
||||
self.fill_groups_combobox(self.add_group_form.parent_group_combobox)
|
||||
self.main_window.increment_progress_bar()
|
||||
self.main_window.finished_progress_bar()
|
||||
self.application.set_normal_cursor()
|
||||
self.list_view.blockSignals(False)
|
||||
|
||||
def add_sub_groups(self, group_list, parent_group_id):
|
||||
"""
|
||||
Recursively add subgroups to the given parent group in a QTreeWidget.
|
||||
|
||||
:param group_list: The List object that contains all QTreeWidgetItems.
|
||||
:param parent_group_id: The ID of the group that will be added recursively.
|
||||
"""
|
||||
image_groups = self.manager.get_all_objects(ImageGroups, ImageGroups.parent_id == parent_group_id)
|
||||
image_groups.sort(key=lambda group_object: get_natural_key(group_object.group_name))
|
||||
folder_icon = UiIcons().folder
|
||||
for image_group in image_groups:
|
||||
group = QtWidgets.QTreeWidgetItem()
|
||||
group.setText(0, image_group.group_name)
|
||||
group.setData(0, QtCore.Qt.UserRole, image_group)
|
||||
group.setIcon(0, folder_icon)
|
||||
if parent_group_id == 0:
|
||||
self.list_view.addTopLevelItem(group)
|
||||
else:
|
||||
group_list[parent_group_id].addChild(group)
|
||||
group_list[image_group.id] = group
|
||||
self.add_sub_groups(group_list, image_group.id)
|
||||
|
||||
def fill_groups_combobox(self, combobox, parent_group_id=0, prefix=''):
|
||||
"""
|
||||
Recursively add groups to the combobox in the 'Add group' dialog.
|
||||
|
||||
:param combobox: The QComboBox to add the options to.
|
||||
:param parent_group_id: The ID of the group that will be added.
|
||||
:param prefix: A string containing the prefix that will be added in front of the groupname for each level of
|
||||
the tree.
|
||||
"""
|
||||
if parent_group_id == 0:
|
||||
combobox.clear()
|
||||
combobox.top_level_group_added = False
|
||||
image_groups = self.manager.get_all_objects(ImageGroups, ImageGroups.parent_id == parent_group_id)
|
||||
image_groups.sort(key=lambda group_object: get_natural_key(group_object.group_name))
|
||||
for image_group in image_groups:
|
||||
combobox.addItem(prefix + image_group.group_name, image_group.id)
|
||||
self.fill_groups_combobox(combobox, image_group.id, prefix + ' ')
|
||||
|
||||
def expand_group(self, group_id, root_item=None):
|
||||
"""
|
||||
Expand groups in the widget recursively.
|
||||
|
||||
:param group_id: The ID of the group that will be expanded.
|
||||
:param root_item: This option is only used for recursion purposes.
|
||||
"""
|
||||
return_value = False
|
||||
if root_item is None:
|
||||
root_item = self.list_view.invisibleRootItem()
|
||||
for i in range(root_item.childCount()):
|
||||
child = root_item.child(i)
|
||||
if self.expand_group(group_id, child):
|
||||
child.setExpanded(True)
|
||||
return_value = True
|
||||
if isinstance(root_item.data(0, QtCore.Qt.UserRole), ImageGroups):
|
||||
if root_item.data(0, QtCore.Qt.UserRole).id == group_id:
|
||||
return True
|
||||
return return_value
|
||||
|
||||
def generate_thumbnail_path(self, image):
|
||||
def generate_thumbnail_path(self, item: Item) -> Path:
|
||||
"""
|
||||
Generate a path to the thumbnail
|
||||
|
||||
:param openlp.plugins.images.lib.db.ImageFilenames image: The image to generate the thumbnail path for.
|
||||
:param openlp.plugins.images.lib.db.Item image: The image to generate the thumbnail path for.
|
||||
:return: A path to the thumbnail
|
||||
:rtype: Path
|
||||
"""
|
||||
ext = image.file_path.suffix.lower()
|
||||
return self.service_path / '{name:s}{ext}'.format(name=image.file_hash or image.file_path.stem, ext=ext)
|
||||
file_path = Path(item.file_path)
|
||||
ext = file_path.suffix.lower()
|
||||
return self.service_path / '{name:s}{ext}'.format(name=item.file_hash or file_path.stem, ext=ext)
|
||||
|
||||
def load_full_list(self, images, initial_load=False, open_group=None):
|
||||
"""
|
||||
Replace the list of images and groups in the interface.
|
||||
|
||||
:param list[openlp.plugins.images.lib.db.ImageFilenames] images: A List of Image Filenames objects that will be
|
||||
used to reload the mediamanager list.
|
||||
:param initial_load: When set to False, the busy cursor and progressbar will be shown while loading images.
|
||||
:param open_group: ImageGroups object of the group that must be expanded after reloading the list in the
|
||||
interface.
|
||||
"""
|
||||
if not initial_load:
|
||||
self.application.set_busy_cursor()
|
||||
self.main_window.display_progress_bar(len(images))
|
||||
self.list_view.clear()
|
||||
# Load the list of groups and add them to the treeView.
|
||||
group_items = {}
|
||||
self.add_sub_groups(group_items, parent_group_id=0)
|
||||
if open_group is not None:
|
||||
self.expand_group(open_group.id)
|
||||
# Sort the images by its filename considering language specific.
|
||||
# characters.
|
||||
images.sort(key=lambda image_object: get_natural_key(image_object.file_path.name))
|
||||
for image in images:
|
||||
log.debug('Loading image: {name}'.format(name=image.file_path))
|
||||
file_name = image.file_path.name
|
||||
thumbnail_path = self.generate_thumbnail_path(image)
|
||||
if not image.file_path.exists():
|
||||
icon = UiIcons().delete
|
||||
else:
|
||||
if validate_thumb(image.file_path, thumbnail_path):
|
||||
icon = build_icon(thumbnail_path)
|
||||
else:
|
||||
icon = create_thumb(image.file_path, thumbnail_path)
|
||||
item_name = QtWidgets.QTreeWidgetItem([file_name])
|
||||
item_name.setText(0, file_name)
|
||||
item_name.setIcon(0, icon)
|
||||
item_name.setToolTip(0, str(image.file_path))
|
||||
item_name.setData(0, QtCore.Qt.UserRole, image)
|
||||
if image.group_id == 0:
|
||||
self.list_view.addTopLevelItem(item_name)
|
||||
else:
|
||||
group_items[image.group_id].addChild(item_name)
|
||||
if not initial_load:
|
||||
self.main_window.increment_progress_bar()
|
||||
if not initial_load:
|
||||
self.main_window.finished_progress_bar()
|
||||
self.application.set_normal_cursor()
|
||||
|
||||
def validate_and_load(self, file_paths, target_group=None):
|
||||
"""
|
||||
Process a list for files either from the File Dialog or from Drag and Drop.
|
||||
This method is overloaded from MediaManagerItem.
|
||||
|
||||
:param list[Path] file_paths: A List of paths to be loaded
|
||||
:param target_group: The QTreeWidgetItem of the group that will be the parent of the added files
|
||||
"""
|
||||
self.application.set_normal_cursor()
|
||||
self.load_list(file_paths, target_group)
|
||||
last_dir = file_paths[0].parent
|
||||
self.settings.setValue('images/last directory', last_dir)
|
||||
|
||||
def load_list(self, image_paths, target_group=None, initial_load=False):
|
||||
"""
|
||||
Add new images to the database. This method is called when adding images using the Add button or DnD.
|
||||
|
||||
:param list[Path] image_paths: A list of file paths to the images to be loaded
|
||||
:param target_group: The QTreeWidgetItem of the group that will be the parent of the added files
|
||||
:param initial_load: When set to False, the busy cursor and progressbar will be shown while loading images
|
||||
"""
|
||||
parent_group = None
|
||||
if target_group is None:
|
||||
# Find out if a group must be pre-selected
|
||||
preselect_group = None
|
||||
selected_items = self.list_view.selectedItems()
|
||||
if selected_items:
|
||||
selected_item = selected_items[0]
|
||||
if isinstance(selected_item.data(0, QtCore.Qt.UserRole), ImageFilenames):
|
||||
selected_item = selected_item.parent()
|
||||
if isinstance(selected_item, QtWidgets.QTreeWidgetItem):
|
||||
if isinstance(selected_item.data(0, QtCore.Qt.UserRole), ImageGroups):
|
||||
preselect_group = selected_item.data(0, QtCore.Qt.UserRole).id
|
||||
# Enable and disable parts of the 'choose group' form
|
||||
if self.manager.get_object_count(ImageGroups) == 0:
|
||||
self.choose_group_form.existing_radio_button.setDisabled(True)
|
||||
self.choose_group_form.group_combobox.setDisabled(True)
|
||||
else:
|
||||
self.choose_group_form.existing_radio_button.setDisabled(False)
|
||||
self.choose_group_form.group_combobox.setDisabled(False)
|
||||
# Ask which group the image_paths should be saved in
|
||||
if self.choose_group_form.exec(selected_group=preselect_group):
|
||||
if self.choose_group_form.nogroup_radio_button.isChecked():
|
||||
# User chose 'No group'
|
||||
parent_group = ImageGroups()
|
||||
parent_group.id = 0
|
||||
elif self.choose_group_form.existing_radio_button.isChecked():
|
||||
# User chose 'Existing group'
|
||||
group_id = self.choose_group_form.group_combobox.itemData(
|
||||
self.choose_group_form.group_combobox.currentIndex(), QtCore.Qt.UserRole)
|
||||
parent_group = self.manager.get_object_filtered(ImageGroups, ImageGroups.id == group_id)
|
||||
elif self.choose_group_form.new_radio_button.isChecked():
|
||||
# User chose 'New group'
|
||||
parent_group = ImageGroups()
|
||||
parent_group.parent_id = 0
|
||||
parent_group.group_name = self.choose_group_form.new_group_edit.text()
|
||||
self.manager.save_object(parent_group)
|
||||
self.fill_groups_combobox(self.choose_group_form.group_combobox)
|
||||
self.fill_groups_combobox(self.add_group_form.parent_group_combobox)
|
||||
def load_item(self, item: Item, is_initial_load: bool = False) -> QtWidgets.QTreeWidgetItem:
|
||||
"""Given an item object, return a QTreeWidgetItem"""
|
||||
tree_item = None
|
||||
file_path = Path(item.file_path)
|
||||
file_name = file_path.name
|
||||
if not file_path.exists():
|
||||
tree_item = QtWidgets.QTreeWidgetItem([file_name])
|
||||
tree_item.setIcon(0, UiIcons().delete)
|
||||
tree_item.setData(0, QtCore.Qt.UserRole, item)
|
||||
tree_item.setToolTip(0, str(file_path))
|
||||
else:
|
||||
parent_group = target_group.data(0, QtCore.Qt.UserRole)
|
||||
if isinstance(parent_group, ImageFilenames):
|
||||
if parent_group.group_id == 0:
|
||||
parent_group = ImageGroups()
|
||||
parent_group.id = 0
|
||||
else:
|
||||
parent_group = target_group.parent().data(0, QtCore.Qt.UserRole)
|
||||
# If no valid parent group is found, do nothing
|
||||
if not isinstance(parent_group, ImageGroups):
|
||||
return
|
||||
# Initialize busy cursor and progress bar
|
||||
self.application.set_busy_cursor()
|
||||
self.main_window.display_progress_bar(len(image_paths))
|
||||
# Save the new image_paths in the database
|
||||
self.save_new_images_list(image_paths, group_id=parent_group.id, reload_list=False)
|
||||
self.load_full_list(self.manager.get_all_objects(ImageFilenames, order_by_ref=ImageFilenames.file_path),
|
||||
initial_load=initial_load, open_group=parent_group)
|
||||
self.application.set_normal_cursor()
|
||||
log.debug('Loading image: {name}'.format(name=item.file_path))
|
||||
thumbnail_path = self.generate_thumbnail_path(item)
|
||||
if validate_thumb(file_path, thumbnail_path):
|
||||
icon = build_icon(thumbnail_path)
|
||||
else:
|
||||
icon = create_thumb(file_path, thumbnail_path)
|
||||
tree_item = QtWidgets.QTreeWidgetItem([file_name])
|
||||
tree_item.setData(0, QtCore.Qt.UserRole, item)
|
||||
tree_item.setIcon(0, icon)
|
||||
tree_item.setToolTip(0, str(file_path))
|
||||
return tree_item
|
||||
|
||||
def save_new_images_list(self, image_paths, group_id=0, reload_list=True):
|
||||
def delete_item(self, item: Item):
|
||||
"""
|
||||
Convert a list of image filenames to ImageFilenames objects and save them in the database.
|
||||
|
||||
:param list[Path] image_paths: A List of file paths to image
|
||||
:param group_id: The ID of the group to save the images in
|
||||
:param reload_list: This boolean is set to True when the list in the interface should be reloaded after saving
|
||||
the new images
|
||||
Remove an image item from the list.
|
||||
"""
|
||||
for image_path in image_paths:
|
||||
if not isinstance(image_path, Path):
|
||||
continue
|
||||
log.debug('Adding new image: {name}'.format(name=image_path))
|
||||
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:
|
||||
self.load_full_list(self.manager.get_all_objects(ImageFilenames, order_by_ref=ImageFilenames.file_path))
|
||||
|
||||
def dnd_move_internal(self, target):
|
||||
"""
|
||||
Handle drag-and-drop moving of images within the media manager
|
||||
|
||||
:param target: This contains the QTreeWidget that is the target of the DnD action
|
||||
"""
|
||||
items_to_move = self.list_view.selectedItems()
|
||||
# Determine group to move images to
|
||||
target_group = target
|
||||
if target_group is not None and isinstance(target_group.data(0, QtCore.Qt.UserRole), ImageFilenames):
|
||||
target_group = target.parent()
|
||||
# Move to toplevel
|
||||
if target_group is None:
|
||||
target_group = self.list_view.invisibleRootItem()
|
||||
target_group.setData(0, QtCore.Qt.UserRole, ImageGroups())
|
||||
target_group.data(0, QtCore.Qt.UserRole).id = 0
|
||||
# Move images in the treeview
|
||||
items_to_save = []
|
||||
for item in items_to_move:
|
||||
if isinstance(item.data(0, QtCore.Qt.UserRole), ImageFilenames):
|
||||
if isinstance(item.parent(), QtWidgets.QTreeWidgetItem):
|
||||
item.parent().removeChild(item)
|
||||
else:
|
||||
self.list_view.invisibleRootItem().removeChild(item)
|
||||
target_group.addChild(item)
|
||||
item.setSelected(True)
|
||||
item_data = item.data(0, QtCore.Qt.UserRole)
|
||||
item_data.group_id = target_group.data(0, QtCore.Qt.UserRole).id
|
||||
items_to_save.append(item_data)
|
||||
target_group.setExpanded(True)
|
||||
# Update the group ID's of the images in the database
|
||||
self.manager.save_objects(items_to_save)
|
||||
# Sort the target group
|
||||
group_items = []
|
||||
image_items = []
|
||||
for item in target_group.takeChildren():
|
||||
if isinstance(item.data(0, QtCore.Qt.UserRole), ImageGroups):
|
||||
group_items.append(item)
|
||||
if isinstance(item.data(0, QtCore.Qt.UserRole), ImageFilenames):
|
||||
image_items.append(item)
|
||||
group_items.sort(key=lambda item: get_natural_key(item.text(0)))
|
||||
target_group.addChildren(group_items)
|
||||
image_items.sort(key=lambda item: get_natural_key(item.text(0)))
|
||||
target_group.addChildren(image_items)
|
||||
file_path = Path(item.file_path)
|
||||
if file_path.exists():
|
||||
delete_file(self.service_path / file_path.name)
|
||||
delete_file(self.generate_thumbnail_path(item))
|
||||
|
||||
def generate_slide_data(self, service_item, *, item=None, remote=False, context=ServiceItemContext.Service,
|
||||
**kwargs):
|
||||
|
@ -561,7 +193,7 @@ class ImageMediaItem(MediaManagerItem):
|
|||
if not items:
|
||||
return False
|
||||
# Determine service item title
|
||||
if isinstance(items[0].data(0, QtCore.Qt.UserRole), ImageGroups) or len(items) == 1:
|
||||
if isinstance(items[0].data(0, QtCore.Qt.UserRole), Folder) or len(items) == 1:
|
||||
service_item.title = items[0].text(0)
|
||||
else:
|
||||
service_item.title = str(self.plugin.name_strings['plural'])
|
||||
|
@ -583,19 +215,19 @@ class ImageMediaItem(MediaManagerItem):
|
|||
existing_images = []
|
||||
# Expand groups to images
|
||||
for bitem in items:
|
||||
if isinstance(bitem.data(0, QtCore.Qt.UserRole), ImageGroups) or bitem.data(0, QtCore.Qt.UserRole) is None:
|
||||
if isinstance(bitem.data(0, QtCore.Qt.UserRole), Folder) or bitem.data(0, QtCore.Qt.UserRole) is None:
|
||||
for index in range(0, bitem.childCount()):
|
||||
if isinstance(bitem.child(index).data(0, QtCore.Qt.UserRole), ImageFilenames):
|
||||
if isinstance(bitem.child(index).data(0, QtCore.Qt.UserRole), Item):
|
||||
images.append(bitem.child(index).data(0, QtCore.Qt.UserRole))
|
||||
elif isinstance(bitem.data(0, QtCore.Qt.UserRole), ImageFilenames):
|
||||
elif isinstance(bitem.data(0, QtCore.Qt.UserRole), Item):
|
||||
images.append(bitem.data(0, QtCore.Qt.UserRole))
|
||||
# Don't try to display empty groups
|
||||
if not images:
|
||||
return False
|
||||
# Find missing files
|
||||
for image in images:
|
||||
if not image.file_path.exists():
|
||||
missing_items_file_names.append(str(image.file_path))
|
||||
if not Path(image.file_path).exists():
|
||||
missing_items_file_names.append(image.file_path)
|
||||
else:
|
||||
existing_images.append(image)
|
||||
# We cannot continue, as all images do not exist.
|
||||
|
@ -616,55 +248,11 @@ class ImageMediaItem(MediaManagerItem):
|
|||
return False
|
||||
# Continue with the existing images.
|
||||
for image in existing_images:
|
||||
name = image.file_path.name
|
||||
name = Path(image.file_path).name
|
||||
thumbnail_path = self.generate_thumbnail_path(image)
|
||||
service_item.add_from_image(image.file_path, name, thumbnail_path)
|
||||
service_item.add_from_image(Path(image.file_path), name, thumbnail_path)
|
||||
return True
|
||||
|
||||
def check_group_exists(self, new_group):
|
||||
"""
|
||||
Returns *True* if the given Group already exists in the database, otherwise *False*.
|
||||
|
||||
:param new_group: The ImageGroups object that contains the name of the group that will be checked
|
||||
"""
|
||||
groups = self.manager.get_all_objects(ImageGroups, ImageGroups.group_name == new_group.group_name)
|
||||
if groups:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def on_add_group_click(self):
|
||||
"""
|
||||
Called to add a new group
|
||||
"""
|
||||
# Find out if a group must be pre-selected
|
||||
preselect_group = 0
|
||||
selected_items = self.list_view.selectedItems()
|
||||
if selected_items:
|
||||
selected_item = selected_items[0]
|
||||
if isinstance(selected_item.data(0, QtCore.Qt.UserRole), ImageFilenames):
|
||||
selected_item = selected_item.parent()
|
||||
if isinstance(selected_item, QtWidgets.QTreeWidgetItem):
|
||||
if isinstance(selected_item.data(0, QtCore.Qt.UserRole), ImageGroups):
|
||||
preselect_group = selected_item.data(0, QtCore.Qt.UserRole).id
|
||||
# Show 'add group' dialog
|
||||
if self.add_group_form.exec(show_top_level_group=True, selected_group=preselect_group):
|
||||
new_group = ImageGroups(parent_id=self.add_group_form.parent_group_combobox.itemData(
|
||||
self.add_group_form.parent_group_combobox.currentIndex(), QtCore.Qt.UserRole),
|
||||
group_name=self.add_group_form.name_edit.text())
|
||||
if not self.check_group_exists(new_group):
|
||||
if self.manager.save_object(new_group):
|
||||
self.load_full_list(self.manager.get_all_objects(
|
||||
ImageFilenames, order_by_ref=ImageFilenames.file_path))
|
||||
self.expand_group(new_group.id)
|
||||
self.fill_groups_combobox(self.choose_group_form.group_combobox)
|
||||
self.fill_groups_combobox(self.add_group_form.parent_group_combobox)
|
||||
else:
|
||||
critical_error_message_box(
|
||||
message=translate('ImagePlugin.AddGroupForm', 'Could not add the new group.'))
|
||||
else:
|
||||
critical_error_message_box(message=translate('ImagePlugin.AddGroupForm', 'This group already exists.'))
|
||||
|
||||
def on_reset_click(self):
|
||||
"""
|
||||
Called to reset the Live background with the image selected.
|
||||
|
@ -688,10 +276,10 @@ class ImageMediaItem(MediaManagerItem):
|
|||
self.list_view,
|
||||
translate('ImagePlugin.MediaItem', 'You must select an image to replace the background with.')):
|
||||
bitem = self.list_view.selectedItems()[0]
|
||||
if not isinstance(bitem.data(0, QtCore.Qt.UserRole), ImageFilenames):
|
||||
if not isinstance(bitem.data(0, QtCore.Qt.UserRole), Item):
|
||||
# Only continue when an image is selected.
|
||||
return
|
||||
file_path = bitem.data(0, QtCore.Qt.UserRole).file_path
|
||||
file_path = Path(bitem.data(0, QtCore.Qt.UserRole).file_path)
|
||||
if file_path.exists():
|
||||
self.live_controller.set_background_image(file_path)
|
||||
self.reset_action.setVisible(True)
|
||||
|
@ -701,32 +289,3 @@ class ImageMediaItem(MediaManagerItem):
|
|||
UiStrings().LiveBGError,
|
||||
translate('ImagePlugin.MediaItem', 'There was a problem replacing your background, '
|
||||
'the image file "{name}" no longer exists.').format(name=file_path))
|
||||
|
||||
def search(self, string, show_error=True):
|
||||
"""
|
||||
Perform a search on the image file names.
|
||||
|
||||
:param str string: The glob to search for
|
||||
:param bool show_error: Unused.
|
||||
"""
|
||||
files = self.manager.get_all_objects(
|
||||
ImageFilenames, filter_clause=ImageFilenames.file_path.contains(string),
|
||||
order_by_ref=ImageFilenames.file_path)
|
||||
results = []
|
||||
for file_object in files:
|
||||
file_name = file_object.file_path.name
|
||||
results.append([str(file_object.file_path), file_name])
|
||||
return results
|
||||
|
||||
def create_item_from_id(self, item_id):
|
||||
"""
|
||||
Create a media item from an item id. Overridden from the parent method to change the item type.
|
||||
|
||||
:param item_id: Id to make live
|
||||
"""
|
||||
item_id = Path(item_id)
|
||||
item = QtWidgets.QTreeWidgetItem()
|
||||
item_data = self.manager.get_object_filtered(ImageFilenames, ImageFilenames.file_path == item_id)
|
||||
item.setText(0, item_data.file_path.name)
|
||||
item.setData(0, QtCore.Qt.UserRole, item_data)
|
||||
return item
|
||||
|
|
|
@ -26,7 +26,9 @@ import logging
|
|||
import shutil
|
||||
from pathlib import Path
|
||||
|
||||
from sqlalchemy import Column, Table, types
|
||||
from sqlalchemy import Column, ForeignKey, MetaData, Table, inspect
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy.types import Integer, Unicode
|
||||
|
||||
from openlp.core.common import sha256_file_hash
|
||||
from openlp.core.common.applocation import AppLocation
|
||||
|
@ -36,17 +38,17 @@ from openlp.core.lib.db import PathType, get_upgrade_op
|
|||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
__version__ = 3
|
||||
__version__ = 4
|
||||
|
||||
|
||||
def upgrade_1(session, metadata):
|
||||
def upgrade_1(session: Session, metadata: MetaData):
|
||||
"""
|
||||
Version 1 upgrade - old db might/might not be versioned.
|
||||
"""
|
||||
log.debug('Skipping upgrade_1 of files DB - not used')
|
||||
|
||||
|
||||
def upgrade_2(session, metadata):
|
||||
def upgrade_2(session: Session, metadata: MetaData):
|
||||
"""
|
||||
Version 2 upgrade - Move file path from old db to JSON encoded path to new db. Added during 2.5 dev
|
||||
"""
|
||||
|
@ -70,7 +72,7 @@ def upgrade_2(session, metadata):
|
|||
op.drop_column('image_filenames', 'filenames')
|
||||
|
||||
|
||||
def upgrade_3(session, metadata):
|
||||
def upgrade_3(session: Session, metadata: MetaData):
|
||||
"""
|
||||
Version 3 upgrade - add sha256 hash
|
||||
"""
|
||||
|
@ -78,7 +80,7 @@ def upgrade_3(session, metadata):
|
|||
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)))
|
||||
op.add_column('image_filenames', Column('file_hash', Unicode(128)))
|
||||
conn = op.get_bind()
|
||||
results = conn.execute('SELECT * FROM image_filenames')
|
||||
thumb_path = AppLocation.get_data_path() / 'images' / 'thumbnails'
|
||||
|
@ -101,3 +103,58 @@ def upgrade_3(session, metadata):
|
|||
except OSError:
|
||||
log.exception('Failed in renaming image thumb from {oldt} to {newt}'.format(oldt=old_thumb,
|
||||
newt=new_thumb))
|
||||
|
||||
|
||||
def upgrade_4(session: Session, metadata: MetaData):
|
||||
"""
|
||||
Version 4 upgrade - convert to the common folders/items model
|
||||
"""
|
||||
log.debug('Starting upgrade_4 for converting to common folders/items model')
|
||||
op = get_upgrade_op(session)
|
||||
conn = op.get_bind()
|
||||
# Check if the folder table exists
|
||||
table_names = inspect(conn).get_table_names()
|
||||
if 'image_groups' not in table_names:
|
||||
# Bypass this upgrade, it has already been performed
|
||||
return
|
||||
# Get references to the old tables
|
||||
old_folder_table = Table('image_groups', metadata, autoload=True)
|
||||
old_item_table = Table('image_filenames', metadata, autoload=True)
|
||||
# Create the new tables
|
||||
if 'folder' not in table_names:
|
||||
new_folder_table = op.create_table(
|
||||
'folder',
|
||||
Column('id', Integer, primary_key=True),
|
||||
Column('name', Unicode(255), nullable=False, index=True),
|
||||
Column('parent_id', Integer, ForeignKey('folder.id'))
|
||||
)
|
||||
else:
|
||||
new_folder_table = Table('folder', metadata, autoload=True)
|
||||
if 'item' not in table_names:
|
||||
new_item_table = op.create_table(
|
||||
'item',
|
||||
Column('id', Integer, primary_key=True),
|
||||
Column('name', Unicode(255), nullable=False, index=True),
|
||||
Column('file_path', Unicode(255)),
|
||||
Column('file_hash', Unicode(255)),
|
||||
Column('folder_id', Integer)
|
||||
)
|
||||
else:
|
||||
new_item_table = Table('item', metadata, autoload=True)
|
||||
# Bulk insert all the data from the old tables to the new tables
|
||||
folders = []
|
||||
for old_folder in conn.execute(old_folder_table.select()).fetchall():
|
||||
folders.append({'id': old_folder.id, 'name': old_folder.group_name,
|
||||
'parent_id': old_folder.parent_id if old_folder.parent_id != 0 else None})
|
||||
op.bulk_insert(new_folder_table, folders)
|
||||
items = []
|
||||
for old_item in conn.execute(old_item_table.select()).fetchall():
|
||||
file_path = json.loads(old_item.file_path, cls=OpenLPJSONDecoder)
|
||||
items.append({'id': old_item.id, 'name': file_path.name, 'file_path': str(file_path),
|
||||
'file_hash': old_item.file_hash, 'folder_id': old_item.group_id})
|
||||
op.bulk_insert(new_item_table, items)
|
||||
# Remove the old tables
|
||||
del old_item_table
|
||||
del old_folder_table
|
||||
op.drop_table('image_filenames')
|
||||
op.drop_table('image_groups')
|
||||
|
|
|
@ -18,7 +18,8 @@
|
|||
# You should have received a copy of the GNU General Public License #
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||
##########################################################################
|
||||
from unittest.mock import MagicMock
|
||||
from pathlib import Path
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
from PyQt5 import QtCore, QtWidgets
|
||||
|
@ -28,18 +29,20 @@ from openlp.core.ui.library import FolderLibraryItem
|
|||
|
||||
class MockItem(MagicMock):
|
||||
file_path = 'path/to/video.mp4'
|
||||
folder_id = None
|
||||
|
||||
|
||||
class MockFolder(MagicMock):
|
||||
pass
|
||||
parent_id = None
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def folder_library_item(registry, settings):
|
||||
mocked_item = MagicMock()
|
||||
registry.register('main_window', MagicMock())
|
||||
mocked_manager = MagicMock()
|
||||
mocked_manager.get_object_filtered.return_value = mocked_item
|
||||
library_item = FolderLibraryItem(None, MagicMock(manager=mocked_manager), MockFolder, MockItem)
|
||||
with patch('openlp.core.lib.mediamanageritem.MediaManagerItem._setup'), \
|
||||
patch('openlp.core.lib.mediamanageritem.MediaManagerItem.setup_item'):
|
||||
library_item = FolderLibraryItem(None, MagicMock(manager=mocked_manager), MockFolder, MockItem)
|
||||
return library_item
|
||||
|
||||
|
||||
|
@ -56,32 +59,28 @@ def test_folderlibrary_retranslate_ui(folder_library_item):
|
|||
folder_library_item.add_folder_action.setToolTip.assert_called_once_with('Add folder.')
|
||||
|
||||
|
||||
def test_folderlibrary_create_item_from_id_path(registry, settings):
|
||||
def test_folderlibrary_create_item_from_id_path(folder_library_item):
|
||||
"""Test the create_item_from_id method"""
|
||||
# GIVEN: An instance of the FolderLibraryItem
|
||||
mocked_item = MagicMock()
|
||||
mocked_manager = MagicMock()
|
||||
mocked_manager.get_object_filtered.return_value = mocked_item
|
||||
library_item = FolderLibraryItem(None, MagicMock(manager=mocked_manager), MockFolder, MockItem)
|
||||
mocked_item = MockItem()
|
||||
folder_library_item.manager.get_object_filtered.return_value = mocked_item
|
||||
|
||||
# WHEN: create_item_from_id is called
|
||||
result = library_item.create_item_from_id('path/to/video.mp4')
|
||||
result = folder_library_item.create_item_from_id('path/to/video.mp4')
|
||||
|
||||
# THEN: The result should be a QTreeWidgetItem with a mocked object as data
|
||||
assert isinstance(result, QtWidgets.QTreeWidgetItem)
|
||||
assert result.data(0, QtCore.Qt.UserRole) is mocked_item
|
||||
|
||||
|
||||
def test_folderlibrary_create_item_from_id_object(registry, settings):
|
||||
def test_folderlibrary_create_item_from_id_object(folder_library_item):
|
||||
"""Test the create_item_from_id method"""
|
||||
# GIVEN: An instance of the FolderLibraryItem
|
||||
mocked_item = MagicMock()
|
||||
mocked_manager = MagicMock()
|
||||
mocked_manager.get_object_filtered.return_value = mocked_item
|
||||
library_item = FolderLibraryItem(None, MagicMock(manager=mocked_manager), MockFolder, MockItem)
|
||||
mocked_item = MockItem()
|
||||
folder_library_item.manager.get_object_filtered.return_value = mocked_item
|
||||
|
||||
# WHEN: create_item_from_id is called
|
||||
result = library_item.create_item_from_id(mocked_item)
|
||||
result = folder_library_item.create_item_from_id(mocked_item)
|
||||
|
||||
# THEN: The result should be a QTreeWidgetItem with a mocked object as data
|
||||
assert isinstance(result, QtWidgets.QTreeWidgetItem)
|
||||
|
@ -103,3 +102,83 @@ def test_folderlibrary_current_folder(folder_library_item):
|
|||
|
||||
# THEN: The current folder should be the mocked folder
|
||||
assert folder is mocked_folder
|
||||
|
||||
|
||||
def test_validate_and_load(folder_library_item):
|
||||
"""
|
||||
Test that the validate_and_load() method when called with a folder
|
||||
"""
|
||||
# GIVEN: A list of files, and a mocked out load_list
|
||||
file_list = ['path1/image1.jpg', 'path2/image2.jpg']
|
||||
expected_list = [MagicMock(file_path=fp) for fp in file_list]
|
||||
folder_library_item.manager.get_all_objects.return_value = expected_list
|
||||
folder_library_item.load_list = MagicMock()
|
||||
folder_library_item.list_view = MagicMock(**{'selectedItems.return_value': None})
|
||||
folder_library_item.settings_section = 'tests'
|
||||
folder_library_item.choose_folder_form = MagicMock(**{'exec.return_value': QtWidgets.QDialog.Accepted,
|
||||
'folder': None})
|
||||
|
||||
# WHEN: Calling validate_and_load with the list of files and a group
|
||||
folder_library_item.validate_and_load(file_list)
|
||||
|
||||
# THEN: load_list should have been called with the file list and the group name,
|
||||
# the directory should have been saved to the settings
|
||||
folder_library_item.load_list.assert_called_once_with(expected_list, target_folder=None)
|
||||
|
||||
|
||||
@patch('openlp.core.lib.serviceitem.sha256_file_hash')
|
||||
def test_recursively_delete_folder(mocked_sha256_file_hash, folder_library_item):
|
||||
"""
|
||||
Test that recursively_delete_folder() works
|
||||
"""
|
||||
# GIVEN: A Folder object and mocked functions
|
||||
folder_library_item.manager.get_all_objects.side_effect = [
|
||||
[MockItem(), MockItem(), MockItem()],
|
||||
[MockFolder()],
|
||||
[MockItem(), MockItem()],
|
||||
[]
|
||||
]
|
||||
folder_library_item.service_path = Path()
|
||||
folder_library_item.manager.delete_object = MagicMock()
|
||||
folder_library_item.delete_item = MagicMock()
|
||||
mocked_folder = MockFolder()
|
||||
mocked_folder.id = 1
|
||||
mocked_folder.parent_id = None
|
||||
mocked_sha256_file_hash.return_value = 'abcd'
|
||||
|
||||
# WHEN: recursively_delete_group() is called
|
||||
folder_library_item.recursively_delete_folder(mocked_folder)
|
||||
|
||||
# THEN: delete_file() should have been called 12 times and manager.delete_object() 7 times.
|
||||
assert folder_library_item.manager.delete_object.call_count == 6, \
|
||||
'delete_object() should be called 6 times, called {} times'.format(
|
||||
folder_library_item.manager.delete_object.call_count)
|
||||
assert folder_library_item.delete_item.call_count == 5, 'delete_item() should have been called 5 times'
|
||||
|
||||
|
||||
@patch('openlp.core.lib.serviceitem.sha256_file_hash')
|
||||
def test_on_delete_click(mocked_sha256_file_hash, folder_library_item):
|
||||
"""
|
||||
Test that on_delete_click() works
|
||||
"""
|
||||
# GIVEN: An ImageGroups object and mocked functions
|
||||
folder_library_item.check_item_selected = MagicMock(return_value=True)
|
||||
folder_library_item.delete_item = MagicMock()
|
||||
mocked_item = MockItem()
|
||||
mocked_item.id = 1
|
||||
mocked_item.group_id = 1
|
||||
mocked_item.file_path = 'imagefile.png'
|
||||
mocked_item.file_hash = 'abcd'
|
||||
folder_library_item.service_path = Path()
|
||||
folder_library_item.list_view = MagicMock()
|
||||
mocked_row_item = MagicMock()
|
||||
mocked_row_item.data.return_value = mocked_item
|
||||
mocked_row_item.text.return_value = ''
|
||||
folder_library_item.list_view.selectedItems.return_value = [mocked_row_item]
|
||||
mocked_sha256_file_hash.return_value = 'abcd'
|
||||
|
||||
# WHEN: Calling on_delete_click
|
||||
folder_library_item.on_delete_click()
|
||||
|
||||
# THEN: delete_file should have been called twice
|
||||
assert folder_library_item.delete_item.call_count == 1, 'delete_item() should have been called once'
|
||||
|
|
|
@ -21,16 +21,14 @@
|
|||
"""
|
||||
This module contains tests for the lib submodule of the Images plugin.
|
||||
"""
|
||||
import pytest
|
||||
from pathlib import Path
|
||||
from unittest.mock import ANY, MagicMock, patch
|
||||
|
||||
from PyQt5 import QtCore, QtWidgets
|
||||
import pytest
|
||||
|
||||
from openlp.core.common.registry import Registry
|
||||
from openlp.core.common.enum import ImageThemeMode
|
||||
from openlp.core.lib.serviceitem import ItemCapabilities
|
||||
from openlp.plugins.images.lib.db import ImageFilenames, ImageGroups
|
||||
from openlp.plugins.images.lib.mediaitem import ImageMediaItem
|
||||
|
||||
|
||||
|
@ -42,136 +40,14 @@ def media_item(mock_settings):
|
|||
Registry().register('main_window', mocked_main_window)
|
||||
Registry().register('live_controller', MagicMock())
|
||||
mocked_plugin = MagicMock()
|
||||
with patch('openlp.plugins.images.lib.mediaitem.MediaManagerItem._setup'), \
|
||||
with patch('openlp.plugins.images.lib.mediaitem.FolderLibraryItem._setup'), \
|
||||
patch('openlp.plugins.images.lib.mediaitem.ImageMediaItem.setup_item'):
|
||||
m_item = ImageMediaItem(None, mocked_plugin)
|
||||
m_item.settings_section = 'images'
|
||||
m_item.list_view = MagicMock()
|
||||
return m_item
|
||||
|
||||
|
||||
def _recursively_delete_group_side_effect(*args, **kwargs):
|
||||
"""
|
||||
Side effect method that creates custom return values for the recursively_delete_group method
|
||||
"""
|
||||
if args[0] == ImageFilenames and args[1]:
|
||||
# Create some fake objects that should be removed
|
||||
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
|
||||
ImageGroups.parent_id = 0
|
||||
# Create a fake group that will be used in the next run
|
||||
returned_object1 = ImageGroups()
|
||||
returned_object1.id = 1
|
||||
return [returned_object1]
|
||||
return []
|
||||
|
||||
|
||||
@patch('openlp.plugins.images.lib.mediaitem.ImageMediaItem.load_full_list')
|
||||
def test_save_new_images_list_empty_list(mocked_load_full_list, media_item):
|
||||
"""
|
||||
Test that the save_new_images_list() method handles empty lists gracefully
|
||||
"""
|
||||
# GIVEN: An empty image_list
|
||||
image_list = []
|
||||
media_item.manager = MagicMock()
|
||||
|
||||
# WHEN: We run save_new_images_list with the empty list
|
||||
media_item.save_new_images_list(image_list)
|
||||
|
||||
# THEN: The save_object() method should not have been called
|
||||
assert media_item.manager.save_object.call_count == 0, \
|
||||
'The save_object() method should not have been called'
|
||||
|
||||
|
||||
@patch('openlp.plugins.images.lib.mediaitem.ImageMediaItem.load_full_list')
|
||||
@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
|
||||
"""
|
||||
# GIVEN: A list with 1 image and a mocked out manager
|
||||
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)
|
||||
|
||||
# THEN: load_full_list() should have been called
|
||||
assert mocked_load_full_list.call_count == 1, 'load_full_list() should have been called'
|
||||
|
||||
# CLEANUP: Remove added attribute from ImageFilenames
|
||||
delattr(ImageFilenames, 'file_path')
|
||||
|
||||
|
||||
@patch('openlp.plugins.images.lib.mediaitem.ImageMediaItem.load_full_list')
|
||||
@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)
|
||||
|
||||
# THEN: load_full_list() should not have been called
|
||||
assert mocked_load_full_list.call_count == 0, 'load_full_list() should not have been called'
|
||||
|
||||
|
||||
@patch('openlp.plugins.images.lib.mediaitem.ImageMediaItem.load_full_list')
|
||||
@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)
|
||||
|
||||
# THEN: load_full_list() should not have been called
|
||||
assert media_item.manager.save_object.call_count == 3, \
|
||||
'load_full_list() should have been called three times'
|
||||
|
||||
|
||||
@patch('openlp.plugins.images.lib.mediaitem.ImageMediaItem.load_full_list')
|
||||
@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)
|
||||
|
||||
# THEN: load_full_list() should not have been called
|
||||
assert media_item.manager.save_object.call_count == 2, 'load_full_list() should have been called only once'
|
||||
|
||||
|
||||
def test_on_reset_click(media_item):
|
||||
"""
|
||||
Test that on_reset_click() actually resets the background
|
||||
|
@ -263,123 +139,6 @@ def test_on_replace_click(mocked_exists, mocked_isinstance, mocked_check_item_se
|
|||
media_item.live_controller.set_background_image.assert_called_with(ANY)
|
||||
|
||||
|
||||
@patch('openlp.plugins.images.lib.mediaitem.delete_file')
|
||||
@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
|
||||
"""
|
||||
# GIVEN: An ImageGroups object and mocked functions
|
||||
ImageFilenames.group_id = 1
|
||||
ImageGroups.parent_id = 1
|
||||
media_item.manager = MagicMock()
|
||||
media_item.manager.get_all_objects.side_effect = _recursively_delete_group_side_effect
|
||||
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)
|
||||
|
||||
# THEN: delete_file() should have been called 12 times and manager.delete_object() 7 times.
|
||||
assert mocked_delete_file.call_count == 12, 'delete_file() should have been called 12 times'
|
||||
assert media_item.manager.delete_object.call_count == 7, \
|
||||
'manager.delete_object() should be called exactly 7 times'
|
||||
|
||||
# CLEANUP: Remove added attribute from Image Filenames and ImageGroups
|
||||
delattr(ImageFilenames, 'group_id')
|
||||
delattr(ImageGroups, 'parent_id')
|
||||
|
||||
|
||||
@patch('openlp.plugins.images.lib.mediaitem.delete_file')
|
||||
@patch('openlp.plugins.images.lib.mediaitem.check_item_selected')
|
||||
@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
|
||||
"""
|
||||
# GIVEN: An ImageGroups object and mocked functions
|
||||
mocked_check_item_selected.return_value = True
|
||||
test_image = ImageFilenames()
|
||||
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()
|
||||
mocked_row_item = MagicMock()
|
||||
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()
|
||||
|
||||
# THEN: delete_file should have been called twice
|
||||
assert mocked_delete_file.call_count == 2, 'delete_file() should have been called twice'
|
||||
|
||||
|
||||
def test_create_item_from_id(media_item):
|
||||
"""
|
||||
Test that the create_item_from_id() method returns a valid QTreeWidgetItem with a pre-created ImageFilenames
|
||||
"""
|
||||
# GIVEN: An ImageFilenames that already exists in the database
|
||||
image_file = ImageFilenames()
|
||||
image_file.id = 1
|
||||
image_file.file_path = Path('/', 'tmp', 'test_file_1.jpg')
|
||||
media_item.manager = MagicMock()
|
||||
media_item.manager.get_object_filtered.return_value = image_file
|
||||
ImageFilenames.file_path = None
|
||||
|
||||
# WHEN: create_item_from_id() is called
|
||||
item = media_item.create_item_from_id('1')
|
||||
|
||||
# THEN: A QTreeWidgetItem should be created with the above model object as it's data
|
||||
assert isinstance(item, QtWidgets.QTreeWidgetItem)
|
||||
assert 'test_file_1.jpg' == item.text(0)
|
||||
item_data = item.data(0, QtCore.Qt.UserRole)
|
||||
assert isinstance(item_data, ImageFilenames)
|
||||
assert 1 == item_data.id
|
||||
assert Path('/', 'tmp', 'test_file_1.jpg') == item_data.file_path
|
||||
|
||||
|
||||
@patch('openlp.plugins.images.lib.mediaitem.ImageMediaItem.load_list')
|
||||
def test_validate_and_load(mocked_load_list, media_item):
|
||||
"""
|
||||
Test that the validate_and_load_test() method when called without a group
|
||||
"""
|
||||
# GIVEN: A list of files
|
||||
file_list = [Path('path1', 'image1.jpg'), Path('path2', 'image2.jpg')]
|
||||
|
||||
# WHEN: Calling validate_and_load with the list of files
|
||||
media_item.validate_and_load(file_list)
|
||||
|
||||
# THEN: load_list should have been called with the file list and None,
|
||||
# the directory should have been saved to the settings
|
||||
mocked_load_list.assert_called_once_with(file_list, None)
|
||||
Registry().get('settings').setValue.assert_called_once_with(ANY, Path('path1'))
|
||||
|
||||
|
||||
@patch('openlp.plugins.images.lib.mediaitem.ImageMediaItem.load_list')
|
||||
def test_validate_and_load_group(mocked_load_list, media_item):
|
||||
"""
|
||||
Test that the validate_and_load_test() method when called with a group
|
||||
"""
|
||||
# GIVEN: A list of files
|
||||
file_list = [Path('path1', 'image1.jpg'), Path('path2', 'image2.jpg')]
|
||||
|
||||
# WHEN: Calling validate_and_load with the list of files and a group
|
||||
media_item.validate_and_load(file_list, 'group')
|
||||
|
||||
# THEN: load_list should have been called with the file list and the group name,
|
||||
# the directory should have been saved to the settings
|
||||
mocked_load_list.assert_called_once_with(file_list, 'group')
|
||||
Registry().get('settings').setValue.assert_called_once_with(ANY, Path('path1'))
|
||||
|
||||
|
||||
def test_generate_thumbnail_path_hash(media_item):
|
||||
"""
|
||||
Test that the thumbnail path is correctly generated with the file hash
|
||||
|
|
Loading…
Reference in New Issue