Merge branch 'async-remote-search' into 'master'

Fix issue #1582 by running the search in the original thread

Closes #1582

See merge request openlp/openlp!641
This commit is contained in:
Raoul Snyman 2023-08-11 22:28:25 +00:00
commit 8052d29fa8
6 changed files with 81 additions and 8 deletions

View File

@ -20,10 +20,11 @@
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
##########################################################################
import logging
import json
import re
from flask import abort, request, Blueprint, jsonify, Response
from PyQt5 import QtCore
from openlp.core.api.lib import login_required, extract_request, old_success_response, old_auth
from openlp.core.lib.plugin import PluginStatus
@ -42,7 +43,14 @@ alert_2_views = Blueprint('v2-alert-plugin', __name__)
def search(plugin_name, text):
plugin = Registry().get('plugin_manager').get_plugin_by_name(plugin_name)
if plugin.status == PluginStatus.Active and plugin.media_item and plugin.media_item.has_search:
results = plugin.media_item.search(text, False)
if hasattr(plugin.media_item.search, '__pyqtSignature__'):
# If this method has a signature, it means that it should be called from the parent thread
results = plugin.media_item.staticMetaObject.invokeMethod(
plugin.media_item, 'search', QtCore.Qt.BlockingQueuedConnection,
QtCore.Q_RETURN_ARG(list), QtCore.Q_ARG(str, text), QtCore.Q_ARG(bool, False))
else:
# Fall back to original behaviour
results = plugin.media_item.search(text, False)
return results
return None

View File

@ -363,7 +363,8 @@ class FolderLibraryItem(MediaManagerItem):
"""
return [item.file_path, item.file_path]
def search(self, string, show_error):
@QtCore.pyqtSlot(str, bool, result=list)
def search(self, string: str, show_error: bool = True) -> list[list[Any]]:
"""
Performs a search for items containing ``string``

View File

@ -22,6 +22,7 @@
import logging
import re
from enum import IntEnum, unique
from typing import Any
from PyQt5 import QtCore, QtWidgets
@ -1072,7 +1073,8 @@ class BibleMediaItem(MediaManagerItem):
else:
return False
def search(self, string, show_error=True):
@QtCore.pyqtSlot(str, bool, result=list)
def search(self, string: str, show_error: bool = True) -> list[list[Any]]:
"""
Search for some Bible verses (by reference).
:param string: search string

View File

@ -18,8 +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/>. #
##########################################################################
import logging
from typing import Any
from PyQt5 import QtCore, QtWidgets
from sqlalchemy.sql import and_, func, or_
@ -348,7 +348,8 @@ class CustomMediaItem(MediaManagerItem):
self.search_text_edit.clear()
self.on_search_text_button_clicked()
def search(self, string, show_error):
@QtCore.pyqtSlot(str, bool, result=list)
def search(self, string: str, show_error: bool = True) -> list[list[Any]]:
"""
Search the database for a given item.

View File

@ -23,6 +23,7 @@ import logging
import mako
import os
from shutil import copyfile
from typing import Any
from PyQt5 import QtCore, QtWidgets
from sqlalchemy.sql import and_, or_
@ -841,7 +842,8 @@ class SongMediaItem(MediaManagerItem):
# List must be empty at the end
return not author_list
def search(self, string, show_error=True):
@QtCore.pyqtSlot(str, bool, result=list)
def search(self, string: str, show_error: bool = True) -> list[list[Any]]:
"""
Search for some songs
:param string: The string to show

View File

@ -22,16 +22,75 @@ from collections import namedtuple
from pathlib import Path
from unittest.mock import MagicMock
from PyQt5 import QtCore
from openlp.core.common.registry import Registry
from openlp.core.common.enum import PluginStatus
from openlp.core.display.render import Renderer
from openlp.core.lib.serviceitem import ServiceItem
from openlp.core.state import State
from openlp.core.api.versions.v2.plugins import search
from tests.openlp_core.lib.test_serviceitem import TEST_PATH as SERVICEITEM_TEST_PATH
from tests.utils import convert_file_service_item
# Search options tests
def test_search_threaded(registry, settings):
"""Test that the search function calls the search method correctly when threaded"""
# GIVEN: A mocked plugin, and plugin manager
mocked_songs_plugin = MagicMock()
mocked_songs_plugin.status = PluginStatus.Active
mocked_songs_plugin.media_item.has_search = True
mocked_songs_plugin.media_item.search.__pyqtSignature__ = 'slot'
mocked_songs_plugin.media_item.staticMetaObject.invokeMethod.return_value = [[1, 'Test', 'This is a test']]
mocked_plugin_manager = MagicMock(**{'get_plugin_by_name.return_value': mocked_songs_plugin})
Registry().register('plugin_manager', mocked_plugin_manager)
# WHEN: the search function is called
result = search('songs', 'test')
# THEN: A song should be returned via the invokeMethod method
assert result == [[1, 'Test', 'This is a test']]
mocked_songs_plugin.media_item.staticMetaObject.invokeMethod.assert_called_once()
invoke_call = mocked_songs_plugin.media_item.staticMetaObject.invokeMethod.call_args_list[0]
assert invoke_call.args[0] is mocked_songs_plugin.media_item
assert invoke_call.args[1] == 'search'
assert invoke_call.args[2] == QtCore.Qt.BlockingQueuedConnection
def test_search_unthreaded(registry, settings):
"""Test that the search function calls the search method correctly when we don't care about threads"""
# GIVEN: A mocked plugin, and plugin manager
mocked_songs_plugin = MagicMock()
mocked_songs_plugin.status = PluginStatus.Active
mocked_songs_plugin.media_item.has_search = True
mocked_songs_plugin.media_item.search.return_value = [[1, 'Test', 'This is a test']]
mocked_plugin_manager = MagicMock(**{'get_plugin_by_name.return_value': mocked_songs_plugin})
Registry().register('plugin_manager', mocked_plugin_manager)
# WHEN: the search function is called
result = search('songs', 'test')
# THEN: A song should be returned via the regular search method
assert result == [[1, 'Test', 'This is a test']]
mocked_songs_plugin.media_item.staticMetaObject.invokeMethod.assert_not_called()
mocked_songs_plugin.media_item.search.assert_called_once_with('test', False)
def test_search_nothing(registry, settings):
"""Test that the search function returns None when there are no plugins by that name enabled"""
# GIVEN: A mocked plugin, and plugin manager
mocked_songs_plugin = MagicMock()
mocked_songs_plugin.status = PluginStatus.Disabled
mocked_plugin_manager = MagicMock(**{'get_plugin_by_name.return_value': mocked_songs_plugin})
Registry().register('plugin_manager', mocked_plugin_manager)
# WHEN: the search function is called
result = search('songs', 'test')
# THEN: None should be returned
assert result is None
def test_bibles_search_options_returns_list(flask_client, settings):
"""
Test a list is returned when a plugin's search options are requested.