This commit is contained in:
Raoul Snyman 2018-04-11 09:08:34 -07:00
commit b521b9aeba
27 changed files with 617 additions and 160 deletions

View File

@ -1 +1 @@
2.5.0
2.9.0

View File

@ -91,6 +91,7 @@ def controller_text(request):
item['text'] = str(frame['title'])
item['html'] = str(frame['title'])
item['selected'] = (live_controller.selected_row == index)
item['title'] = current_item.title
data.append(item)
json_data = {'results': {'slides': data}}
if current_item:
@ -117,12 +118,11 @@ def controller_set(request):
return {'results': {'success': True}}
@controller_endpoint.route('{action:next|previous}')
@controller_endpoint.route('{controller}/{action:next|previous}')
@requires_auth
def controller_direction(request, controller, action):
"""
Handles requests for setting service items in the slide controller
11
:param request: The http request object.
:param controller: the controller slides forward or backward.
:param action: the controller slides forward or backward.
@ -137,7 +137,7 @@ def controller_direction(request, controller, action):
def controller_direction_api(request, controller, action):
"""
Handles requests for setting service items in the slide controller
11
:param request: The http request object.
:param controller: the controller slides forward or backward.
:param action: the controller slides forward or backward.

View File

@ -38,7 +38,6 @@ from PyQt5 import QtCore, QtWidgets, QtWebEngineWidgets
from openlp.core.common import is_macosx, is_win
from openlp.core.common.applocation import AppLocation
from openlp.core.common.i18n import LanguageManager, UiStrings, translate
from openlp.core.common.mixins import LogMixin
from openlp.core.common.path import create_paths, copytree
from openlp.core.common.registry import Registry
from openlp.core.common.settings import Settings
@ -50,6 +49,7 @@ from openlp.core.ui.firsttimeform import FirstTimeForm
from openlp.core.ui.firsttimelanguageform import FirstTimeLanguageForm
from openlp.core.ui.mainwindow import MainWindow
from openlp.core.ui.style import get_application_stylesheet
from openlp.core.server import Server
from openlp.core.version import check_for_update, get_version
__all__ = ['OpenLP', 'main']
@ -58,7 +58,7 @@ __all__ = ['OpenLP', 'main']
log = logging.getLogger()
class OpenLP(QtWidgets.QApplication, LogMixin):
class OpenLP(QtWidgets.QApplication):
"""
The core application class. This class inherits from Qt's QApplication
class in order to provide the core of the application.
@ -72,7 +72,7 @@ class OpenLP(QtWidgets.QApplication, LogMixin):
"""
self.is_event_loop_active = True
result = QtWidgets.QApplication.exec()
self.shared_memory.detach()
self.server.close_server()
return result
def run(self, args):
@ -135,23 +135,16 @@ class OpenLP(QtWidgets.QApplication, LogMixin):
self.main_window.app_startup()
return self.exec()
def is_already_running(self):
@staticmethod
def is_already_running():
"""
Look to see if OpenLP is already running and ask if a 2nd instance is to be started.
Tell the user there is a 2nd instance running.
"""
self.shared_memory = QtCore.QSharedMemory('OpenLP')
if self.shared_memory.attach():
status = QtWidgets.QMessageBox.critical(None, UiStrings().Error, UiStrings().OpenLPStart,
QtWidgets.QMessageBox.StandardButtons(QtWidgets.QMessageBox.Yes |
QtWidgets.QMessageBox.No))
if status == QtWidgets.QMessageBox.No:
return True
return False
else:
self.shared_memory.create(1)
return False
QtWidgets.QMessageBox.critical(None, UiStrings().Error, UiStrings().OpenLPStart,
QtWidgets.QMessageBox.StandardButtons(QtWidgets.QMessageBox.Ok))
def is_data_path_missing(self):
@staticmethod
def is_data_path_missing():
"""
Check if the data folder path exists.
"""
@ -301,10 +294,7 @@ def parse_options(args=None):
parser.add_argument('-l', '--log-level', dest='loglevel', default='warning', metavar='LEVEL',
help='Set logging to LEVEL level. Valid values are "debug", "info", "warning".')
parser.add_argument('-p', '--portable', dest='portable', action='store_true',
help='Specify if this should be run as a portable app, '
'off a USB flash drive (not implemented).')
parser.add_argument('-d', '--dev-version', dest='dev_version', action='store_true',
help='Ignore the version file and pull the version directly from Bazaar')
help='Specify if this should be run as a portable app, ')
parser.add_argument('-w', '--no-web-server', dest='no_web_server', action='store_true',
help='Turn off the Web and Socket Server ')
parser.add_argument('rargs', nargs='?', default=[])
@ -384,11 +374,17 @@ def main(args=None):
Registry().set_flag('no_web_server', args.no_web_server)
application.setApplicationVersion(get_version()['version'])
# Check if an instance of OpenLP is already running. Quit if there is a running instance and the user only wants one
if application.is_already_running():
server = Server()
if server.is_another_instance_running():
application.is_already_running()
server.post_to_server(qt_args)
sys.exit()
else:
server.start_server()
application.server = server
# If the custom data path is missing and the user wants to restore the data path, quit OpenLP.
if application.is_data_path_missing():
application.shared_memory.detach()
server.close_server()
sys.exit()
# Upgrade settings.
settings = Settings()

View File

@ -62,7 +62,7 @@ def get_local_ip4():
"""
# Get the local IPv4 active address(es) that are NOT localhost (lo or '127.0.0.1')
log.debug('Getting local IPv4 interface(es) information')
MY_IP4 = {}
my_ip4 = {}
for iface in QNetworkInterface.allInterfaces():
if not iface.isValid() or not (iface.flags() & (QNetworkInterface.IsUp | QNetworkInterface.IsRunning)):
continue
@ -70,25 +70,25 @@ def get_local_ip4():
ip = address.ip()
# NOTE: Next line will skip if interface is localhost - keep for now until we decide about it later
# if (ip.protocol() == QAbstractSocket.IPv4Protocol) and (ip != QHostAddress.LocalHost):
if (ip.protocol() == QAbstractSocket.IPv4Protocol):
MY_IP4[iface.name()] = {'ip': ip.toString(),
if ip.protocol() == QAbstractSocket.IPv4Protocol:
my_ip4[iface.name()] = {'ip': ip.toString(),
'broadcast': address.broadcast().toString(),
'netmask': address.netmask().toString(),
'prefix': address.prefixLength(),
'localnet': QHostAddress(address.netmask().toIPv4Address() &
ip.toIPv4Address()).toString()
ip.toIPv4Address()).toString()
}
log.debug('Adding {iface} to active list'.format(iface=iface.name()))
if len(MY_IP4) == 1:
if 'lo' in MY_IP4:
if len(my_ip4) == 1:
if 'lo' in my_ip4:
# No active interfaces - so leave localhost in there
log.warning('No active IPv4 interfaces found except localhost')
else:
# Since we have a valid IP4 interface, remove localhost
log.debug('Found at least one IPv4 interface, removing localhost')
MY_IP4.pop('lo')
my_ip4.pop('lo')
return MY_IP4
return my_ip4
def trace_error_handler(logger):

View File

@ -415,7 +415,7 @@ class UiStrings(object):
self.NoResults = translate('OpenLP.Ui', 'No Search Results')
self.OpenLP = translate('OpenLP.Ui', 'OpenLP')
self.OpenLPv2AndUp = translate('OpenLP.Ui', 'OpenLP 2.0 and up')
self.OpenLPStart = translate('OpenLP.Ui', 'OpenLP is already running. Do you wish to continue?')
self.OpenLPStart = translate('OpenLP.Ui', 'OpenLP is already running on this machine. \nClosing this instance')
self.OpenService = translate('OpenLP.Ui', 'Open service.')
self.OptionalShowInFooter = translate('OpenLP.Ui', 'Optional, this will be displayed in footer.')
self.OptionalHideInFooter = translate('OpenLP.Ui', 'Optional, this won\'t be displayed in footer.')

View File

@ -121,6 +121,9 @@ class ItemCapabilities(object):
``HasThumbnails``
The item has related thumbnails available
``HasMetaData``
The item has Meta Data about item
"""
CanPreview = 1
CanEdit = 2
@ -143,6 +146,7 @@ class ItemCapabilities(object):
HasDisplayTitle = 19
HasNotes = 20
HasThumbnails = 21
HasMetaData = 22
class ServiceItem(RegistryProperties):
@ -201,6 +205,7 @@ class ServiceItem(RegistryProperties):
self.will_auto_start = False
self.has_original_files = True
self._new_item()
self.metadata = []
def _new_item(self):
"""
@ -376,7 +381,8 @@ class ServiceItem(RegistryProperties):
'background_audio': self.background_audio,
'theme_overwritten': self.theme_overwritten,
'will_auto_start': self.will_auto_start,
'processor': self.processor
'processor': self.processor,
'metadata': self.metadata
}
service_data = []
if self.service_item_type == ServiceItemType.Text:
@ -427,6 +433,7 @@ class ServiceItem(RegistryProperties):
self.will_auto_start = header.get('will_auto_start', False)
self.processor = header.get('processor', None)
self.has_original_files = True
self.metadata = header.get('item_meta_data', [])
if 'background_audio' in header:
self.background_audio = []
for file_path in header['background_audio']:

109
openlp/core/server.py Normal file
View File

@ -0,0 +1,109 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2018 OpenLP Developers #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
from PyQt5 import QtCore, QtNetwork
from openlp.core.common.registry import Registry
from openlp.core.common.mixins import LogMixin
class Server(QtCore.QObject, LogMixin):
"""
The local server to handle OpenLP running in more than one instance and allows file
handles to be transferred from the new to the existing one.
"""
def __init__(self):
super(Server, self).__init__()
self.out_socket = QtNetwork.QLocalSocket()
self.server = None
self.id = 'OpenLPDual'
def is_another_instance_running(self):
"""
Check the see if an other instance is running
:return: True of False
"""
# Is there another instance running?
self.out_socket.connectToServer(self.id)
return self.out_socket.waitForConnected()
def post_to_server(self, args):
"""
Post the file name to the over instance
:param args: The passed arguments including maybe a file name
"""
if 'OpenLP' in args:
args.remove('OpenLP')
# Yes, there is.
self.out_stream = QtCore.QTextStream(self.out_socket)
self.out_stream.setCodec('UTF-8')
self.out_socket.write(str.encode("".join(args)))
if not self.out_socket.waitForBytesWritten(10):
raise Exception(str(self.out_socket.errorString()))
self.out_socket.disconnectFromServer()
def start_server(self):
"""
Start the socket server to allow inter app communication
:return:
"""
self.out_socket = None
self.out_stream = None
self.in_socket = None
self.in_stream = None
self.server = QtNetwork.QLocalServer()
self.server.listen(self.id)
self.server.newConnection.connect(self._on_new_connection)
return True
def _on_new_connection(self):
"""
Handle a new connection to the server
:return:
"""
if self.in_socket:
self.in_socket.readyRead.disconnect(self._on_ready_read)
self.in_socket = self.server.nextPendingConnection()
if not self.in_socket:
return
self.in_stream = QtCore.QTextStream(self.in_socket)
self.in_stream.setCodec('UTF-8')
self.in_socket.readyRead.connect(self._on_ready_read)
def _on_ready_read(self):
"""
Read a record passed to the server and pass to the service manager to handle
:return:
"""
msg = self.in_stream.readLine()
if msg:
self.log_debug("socket msg = " + msg)
Registry().get('service_manager').on_load_service_clicked(msg)
def close_server(self):
"""
Shutdown to local socket server and make sure the server is removed.
:return:
"""
if self.server:
self.server.close()
# Make sure the server file is removed.
QtNetwork.QLocalServer.removeServer(self.id)

View File

@ -127,9 +127,6 @@ class UiFirstTimeWizard(object):
self.media_check_box.setChecked(True)
self.media_check_box.setObjectName('media_check_box')
self.plugin_layout.addWidget(self.media_check_box)
self.remote_check_box = QtWidgets.QCheckBox(self.plugin_page)
self.remote_check_box.setObjectName('remote_check_box')
self.plugin_layout.addWidget(self.remote_check_box)
self.song_usage_check_box = QtWidgets.QCheckBox(self.plugin_page)
self.song_usage_check_box.setChecked(True)
self.song_usage_check_box.setObjectName('song_usage_check_box')
@ -138,13 +135,6 @@ class UiFirstTimeWizard(object):
self.alert_check_box.setChecked(True)
self.alert_check_box.setObjectName('alert_check_box')
self.plugin_layout.addWidget(self.alert_check_box)
self.projectors_check_box = QtWidgets.QCheckBox(self.plugin_page)
# If visibility setting for projector panel is True, check the box.
if Settings().value('projector/show after wizard'):
self.projectors_check_box.setChecked(True)
self.projectors_check_box.setObjectName('projectors_check_box')
self.projectors_check_box.clicked.connect(self.on_projectors_check_box_clicked)
self.plugin_layout.addWidget(self.projectors_check_box)
first_time_wizard.setPage(FirstTimePage.Plugins, self.plugin_page)
# The song samples page
self.songs_page = QtWidgets.QWizardPage()
@ -256,13 +246,9 @@ class UiFirstTimeWizard(object):
self.presentation_check_box.setText(translate('OpenLP.FirstTimeWizard',
'Presentations Show .ppt, .odp and .pdf files'))
self.media_check_box.setText(translate('OpenLP.FirstTimeWizard', 'Media Playback of Audio and Video files'))
self.remote_check_box.setText(str(UiStrings().WebDownloadText))
self.song_usage_check_box.setText(translate('OpenLP.FirstTimeWizard', 'Song Usage Monitor'))
self.alert_check_box.setText(translate('OpenLP.FirstTimeWizard',
'Alerts Display informative messages while showing other slides'))
self.projectors_check_box.setText(translate('OpenLP.FirstTimeWizard',
'Projector Controller Control PJLink compatible projects on your'
' network from OpenLP'))
self.no_internet_page.setTitle(translate('OpenLP.FirstTimeWizard', 'No Internet Connection'))
self.no_internet_page.setSubTitle(
translate('OpenLP.FirstTimeWizard', 'Unable to detect an Internet connection.'))

View File

@ -179,7 +179,6 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
"""
Check to see if we have any media Player's available.
"""
log.debug('_check_available_media_players')
controller_dir = os.path.join('core', 'ui', 'media')
# Find all files that do not begin with '.' (lp:#1738047) and end with player.py
glob_pattern = os.path.join(controller_dir, '[!.]*player.py')
@ -460,26 +459,16 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
log.debug('use %s controller' % self.current_media_players[controller.controller_type].display_name)
return True
def media_length(self, service_item):
@staticmethod
def media_length(service_item):
"""
Loads and starts a media item to obtain the media length
Uses Media Info to obtain the media length
:param service_item: The ServiceItem containing the details to be played.
"""
media_info = MediaInfo()
media_info.volume = 0
media_info.file_info = QtCore.QFileInfo(service_item.get_frame_path())
# display = controller.preview_display
suffix = '*.%s' % media_info.file_info.suffix().lower()
used_players = get_media_players()[0]
player = self.media_players[used_players[0]]
if suffix not in player.video_extensions_list and suffix not in player.audio_extensions_list:
# Media could not be loaded correctly
critical_error_message_box(
translate('MediaPlugin.MediaItem', 'Unsupported Media File'),
translate('MediaPlugin.MediaItem', 'File {file_path} not supported using player {player_name}'
).format(file_path=service_item.get_frame_path(), player_name=used_players[0]))
return False
media_data = MediaInfoWrapper.parse(service_item.get_frame_path())
# duration returns in milli seconds
service_item.set_media_length(media_data.tracks[0].duration)
@ -574,16 +563,6 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
if not title:
continue
player = self.media_players[title]
# The system player may not return what files it can play so add it now
# and check whether it can play the file later
if title == 'system':
if not controller.media_info.is_background or controller.media_info.is_background and \
player.can_background:
self.resize(display, player)
if player.load(display):
self.current_media_players[controller.controller_type] = player
controller.media_info.media_type = MediaType.Video
return True
if suffix in player.video_extensions_list:
if not controller.media_info.is_background or controller.media_info.is_background and \
player.can_background:

View File

@ -66,7 +66,15 @@ def get_vlc():
"""
if 'openlp.core.ui.media.vendor.vlc' in sys.modules:
# If VLC has already been imported, no need to do all the stuff below again
return sys.modules['openlp.core.ui.media.vendor.vlc']
is_vlc_available = False
try:
is_vlc_available = bool(sys.modules['openlp.core.ui.media.vendor.vlc'].get_default_instance())
except:
pass
if is_vlc_available:
return sys.modules['openlp.core.ui.media.vendor.vlc']
else:
return None
is_vlc_available = False
try:
if is_macosx():
@ -89,6 +97,7 @@ def get_vlc():
except (ImportError, NameError, NotImplementedError):
pass
except OSError as e:
# this will get raised the first time
if is_win():
if not isinstance(e, WindowsError) and e.winerror != 126:
raise

View File

@ -753,7 +753,7 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi
def context_menu(self, point):
"""
The Right click context menu from the Serviceitem list
The Right click context menu from the Service item list
:param point: The location of the cursor.
"""
@ -1138,7 +1138,6 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi
def on_delete_from_service(self):
"""
Remove the current ServiceItem from the list.
:param field:
"""
item = self.find_service_item()[0]
if item != -1:
@ -1207,6 +1206,9 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi
tips.append('<strong>{text1}: </strong> {text2}'.format(text1=text1, text2=text2))
if item['service_item'].is_capable(ItemCapabilities.HasVariableStartTime):
tips.append(item['service_item'].get_media_time())
if item['service_item'].is_capable(ItemCapabilities.HasMetaData):
for meta in item['service_item'].metadata:
tips.append(meta)
tree_widget_item.setToolTip(0, '<br>'.join(tips))
tree_widget_item.setData(0, QtCore.Qt.UserRole, item['order'])
tree_widget_item.setSelected(item['selected'])
@ -1367,7 +1369,6 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi
def make_preview(self):
"""
Send the current item to the Preview slide controller
:param field:
"""
self.application.set_busy_cursor()
item, child = self.find_service_item()
@ -1392,7 +1393,6 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi
def on_double_click_live(self):
"""
Send the current item to the Live slide controller but triggered by a tablewidget click event.
:param field:
"""
self.list_double_clicked = True
self.make_live()
@ -1401,7 +1401,6 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi
"""
If single click previewing is enabled, and triggered by a tablewidget click event,
start a timeout to verify a double-click hasn't triggered.
:param field:
"""
if Settings().value('advanced/single click service preview'):
if not self.list_double_clicked:
@ -1412,7 +1411,6 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi
def on_single_click_preview_timeout(self):
"""
If a single click ok, but double click not triggered, send the current item to the Preview slide controller.
:param field:
"""
if self.list_double_clicked:
# If a double click has registered, clear it.
@ -1452,7 +1450,6 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi
def remote_edit(self):
"""
Triggers a remote edit to a plugin to allow item to be edited.
:param field:
"""
item = self.find_service_item()[0]
if self.service_items[item]['service_item'].is_capable(ItemCapabilities.CanEdit):
@ -1464,8 +1461,6 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi
def on_service_item_rename(self):
"""
Opens a dialog to rename the service item.
:param field: Not used, but PyQt needs this.
"""
item = self.find_service_item()[0]
if not self.service_items[item]['service_item'].is_capable(ItemCapabilities.CanEditTitle):
@ -1482,7 +1477,6 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi
def create_custom(self):
"""
Saves the current text item as a custom slide
:param field:
"""
item = self.find_service_item()[0]
Registry().execute('custom_create_from_service', self.service_items[item]['service_item'])
@ -1603,8 +1597,6 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi
def on_theme_change_action(self):
"""
Handles theme change events
:param field:
"""
theme = self.sender().objectName()
# No object name means that the "Default" theme is supposed to be used.

View File

@ -318,6 +318,10 @@ class SlideController(QtWidgets.QWidget, LogMixin, RegistryProperties):
tooltip=translate('OpenLP.SlideController',
'Edit and reload song preview.'),
triggers=self.on_edit_song)
self.toolbar.add_toolbar_action('clear', icon=':/general/general_delete.png',
tooltip=translate('OpenLP.SlideController',
'Clear'),
triggers=self.on_clear)
self.controller_layout.addWidget(self.toolbar)
# Build the Media Toolbar
self.media_controller.register_controller(self)
@ -356,7 +360,7 @@ class SlideController(QtWidgets.QWidget, LogMixin, RegistryProperties):
self.audio_time_label.setObjectName('audio_time_label')
self.toolbar.add_toolbar_widget(self.audio_time_label)
self.toolbar.set_widget_visible(AUDIO_LIST, False)
self.toolbar.set_widget_visible(['song_menu'], False)
self.toolbar.set_widget_visible('song_menu', False)
# Screen preview area
self.preview_frame = QtWidgets.QFrame(self.splitter)
self.preview_frame.setGeometry(QtCore.QRect(0, 0, 300, 300 * self.ratio))
@ -412,7 +416,8 @@ class SlideController(QtWidgets.QWidget, LogMixin, RegistryProperties):
self.__add_actions_to_widget(self.controller)
else:
self.preview_widget.doubleClicked.connect(self.on_preview_double_click)
self.toolbar.set_widget_visible(['editSong'], False)
self.toolbar.set_widget_visible('editSong', False)
self.toolbar.set_widget_visible('clear', False)
self.controller.addActions([self.next_item, self.previous_item])
Registry().register_function('slidecontroller_{text}_stop_loop'.format(text=self.type_prefix),
self.on_stop_loop)
@ -673,7 +678,7 @@ class SlideController(QtWidgets.QWidget, LogMixin, RegistryProperties):
self.mediabar.hide()
self.song_menu.hide()
self.toolbar.set_widget_visible(LOOP_LIST, False)
self.toolbar.set_widget_visible(['song_menu'], False)
self.toolbar.set_widget_visible('song_menu', False)
# Reset the button
self.play_slides_once.setChecked(False)
self.play_slides_once.setIcon(build_icon(':/media/media_time.png'))
@ -684,7 +689,7 @@ class SlideController(QtWidgets.QWidget, LogMixin, RegistryProperties):
if item.is_text():
if (Settings().value(self.main_window.songs_settings_section + '/display songbar') and
not self.song_menu.menu().isEmpty()):
self.toolbar.set_widget_visible(['song_menu'], True)
self.toolbar.set_widget_visible('song_menu', True)
if item.is_capable(ItemCapabilities.CanLoop) and len(item.slides) > 1:
self.toolbar.set_widget_visible(LOOP_LIST)
if item.is_media():
@ -709,9 +714,10 @@ class SlideController(QtWidgets.QWidget, LogMixin, RegistryProperties):
# See bug #791050
self.toolbar.hide()
self.mediabar.hide()
self.toolbar.set_widget_visible(['editSong'], False)
self.toolbar.set_widget_visible('editSong', False)
self.toolbar.set_widget_visible('clear', True)
if item.is_capable(ItemCapabilities.CanEdit) and item.from_plugin:
self.toolbar.set_widget_visible(['editSong'])
self.toolbar.set_widget_visible('editSong')
elif item.is_media():
self.mediabar.show()
self.previous_item.setVisible(not item.is_media())
@ -1360,6 +1366,14 @@ class SlideController(QtWidgets.QWidget, LogMixin, RegistryProperties):
if new_item:
self.add_service_item(new_item)
def on_clear(self):
"""
Clear the preview bar.
"""
self.preview_widget.clear_list()
self.toolbar.set_widget_visible('editSong', False)
self.toolbar.set_widget_visible('clear', False)
def on_preview_add_to_service(self):
"""
From the preview display request the Item to be added to service

View File

@ -428,8 +428,8 @@ class ThemeManager(QtWidgets.QWidget, RegistryBase, Ui_ThemeManager, LogMixin, R
self.log_exception('Export Theme Failed')
critical_error_message_box(translate('OpenLP.ThemeManager', 'Theme Export Failed'),
translate('OpenLP.ThemeManager',
'The theme_name export failed because this error occurred: {err}')
.format(err=ose.strerror))
'The {theme_name} export failed because this error occurred: {err}')
.format(theme_name=theme_name, err=ose.strerror))
if theme_path.exists():
theme_path.rmtree(ignore_errors=True)
return False

View File

@ -150,48 +150,12 @@ def get_version():
global APPLICATION_VERSION
if APPLICATION_VERSION:
return APPLICATION_VERSION
if '--dev-version' in sys.argv or '-d' in sys.argv:
# NOTE: The following code is a duplicate of the code in setup.py. Any fix applied here should also be applied
# there.
# Get the revision of this tree.
bzr = Popen(('bzr', 'revno'), stdout=PIPE)
tree_revision, error = bzr.communicate()
tree_revision = tree_revision.decode()
code = bzr.wait()
if code != 0:
raise Exception('Error running bzr log')
# Get all tags.
bzr = Popen(('bzr', 'tags'), stdout=PIPE)
output, error = bzr.communicate()
code = bzr.wait()
if code != 0:
raise Exception('Error running bzr tags')
tags = list(map(bytes.decode, output.splitlines()))
if not tags:
tag_version = '0.0.0'
tag_revision = '0'
else:
# Remove any tag that has "?" as revision number. A "?" as revision number indicates, that this tag is from
# another series.
tags = [tag for tag in tags if tag.split()[-1].strip() != '?']
# Get the last tag and split it in a revision and tag name.
tag_version, tag_revision = tags[-1].split()
# If they are equal, then this tree is tarball with the source for the release. We do not want the revision
# number in the full version.
if tree_revision == tag_revision:
full_version = tag_version.strip()
else:
full_version = '{tag}-bzr{tree}'.format(tag=tag_version.strip(), tree=tree_revision.strip())
else:
# We're not running the development version, let's use the file.
file_path = AppLocation.get_directory(AppLocation.VersionDir) / '.version'
try:
full_version = file_path.read_text().rstrip()
except OSError:
log.exception('Error in version file.')
full_version = '0.0.0-bzr000'
file_path = AppLocation.get_directory(AppLocation.VersionDir) / '.version'
try:
full_version = file_path.read_text().rstrip()
except OSError:
log.exception('Error in version file.')
full_version = '0.0.0-bzr000'
bits = full_version.split('-')
APPLICATION_VERSION = {
'full': full_version,

View File

@ -67,14 +67,20 @@ class OpenLPToolbar(QtWidgets.QToolBar):
"""
Set the visibility for a widget or a list of widgets.
:param widgets: A list of string with widget object names.
:param widgets: A list of strings or individual string with widget object names.
:param visible: The new state as bool.
"""
for handle in widgets:
if handle in self.actions:
self.actions[handle].setVisible(visible)
if isinstance(widgets, list):
for handle in widgets:
if handle in self.actions:
self.actions[handle].setVisible(visible)
else:
log.warning('No handle "%s" in actions list.', str(handle))
else:
if widgets in self.actions:
self.actions[widgets].setVisible(visible)
else:
log.warning('No handle "%s" in actions list.', str(handle))
log.warning('No handle "%s" in actions list.', str(widgets))
def set_widget_enabled(self, widgets, enabled=True):
"""

View File

@ -147,6 +147,14 @@ class ListPreviewWidget(QtWidgets.QTableWidget, RegistryProperties):
self.screen_ratio = screen_ratio
self.__recalculate_layout()
def clear_list(self):
"""
Clear the preview list
:return:
"""
self.setRowCount(0)
self.clear()
def replace_service_item(self, service_item, width, slide_number):
"""
Replace the current preview items with the ones in service_item and display the given slide
@ -157,8 +165,7 @@ class ListPreviewWidget(QtWidgets.QTableWidget, RegistryProperties):
"""
self.service_item = service_item
self.setRowCount(0)
self.clear()
self.setColumnWidth(0, width)
self.clear_list()
row = 0
text = []
for slide_index, slide in enumerate(self.service_item.slides):

View File

@ -30,6 +30,7 @@ from openlp.core.common.registry import Registry
from openlp.core.common.settings import Settings
from openlp.core.lib import MediaManagerItem, ItemCapabilities, ServiceItemContext, PluginStatus, \
check_item_selected
from openlp.core.lib.ui import create_widget_action
from openlp.plugins.custom.forms.editcustomform import EditCustomForm
from openlp.plugins.custom.lib import CustomXMLParser, CustomXMLBuilder
from openlp.plugins.custom.lib.db import CustomSlide
@ -84,6 +85,12 @@ class CustomMediaItem(MediaManagerItem):
Registry().register_function('custom_preview', self.on_preview_click)
Registry().register_function('custom_create_from_service', self.create_from_service_item)
def add_custom_context_actions(self):
create_widget_action(self.list_view, separator=True)
create_widget_action(
self.list_view, text=translate('OpenLP.MediaManagerItem', '&Clone'), icon=':/general/general_clone.png',
triggers=self.on_clone_click)
def config_update(self):
"""
Config has been updated so reload values
@ -243,6 +250,23 @@ class CustomMediaItem(MediaManagerItem):
service_item.raw_footer.append('')
return True
def on_clone_click(self):
"""
Clone the selected Custom item
"""
item = self.list_view.currentItem()
item_id = item.data(QtCore.Qt.UserRole)
old_custom_slide = self.plugin.db_manager.get_object(CustomSlide, item_id)
new_custom_slide = CustomSlide()
new_custom_slide.title = '{title} <{text}>'.format(title=old_custom_slide.title,
text=translate('CustomPlugin.MediaItem',
'copy', 'For item cloning'))
new_custom_slide.text = old_custom_slide.text
new_custom_slide.credits = old_custom_slide.credits
new_custom_slide.theme_name = old_custom_slide.theme_name
self.plugin.db_manager.save_object(new_custom_slide)
self.on_search_text_button_clicked()
def on_search_text_button_clicked(self):
"""
Search the plugin database

View File

@ -164,7 +164,7 @@ class Song(BaseModel):
"""
Add a Songbook Entry to the song if it not yet exists
:param songbook_name: Name of the Songbook.
:param songbook: Name of the Songbook.
:param entry: Entry in the Songbook (usually a number)
"""
for songbook_entry in self.songbook_entries:

View File

@ -572,10 +572,19 @@ class SongMediaItem(MediaManagerItem):
service_item.add_capability(ItemCapabilities.OnLoadUpdate)
service_item.add_capability(ItemCapabilities.AddIfNewItem)
service_item.add_capability(ItemCapabilities.CanSoftBreak)
service_item.add_capability(ItemCapabilities.HasMetaData)
song = self.plugin.manager.get_object(Song, item_id)
service_item.theme = song.theme_name
service_item.edit_id = item_id
verse_list = SongXML().get_verses(song.lyrics)
if Settings().value('songs/add songbook slide') and song.songbook_entries:
first_slide = '\n'
for songbook_entry in song.songbook_entries:
first_slide = first_slide + '{book}/{num}/{pub}\n\n'.format(book=songbook_entry.songbook.name,
num=songbook_entry.entry,
pub=songbook_entry.songbook.publisher)
service_item.add_from_text(first_slide, 'O1')
# no verse list or only 1 space (in error)
verse_tags_translated = False
if VerseType.from_translated_string(str(verse_list[0][0]['type'])) is not None:
@ -622,6 +631,9 @@ class SongMediaItem(MediaManagerItem):
if song.media_files:
service_item.add_capability(ItemCapabilities.HasBackgroundAudio)
service_item.background_audio = [m.file_path for m in song.media_files]
item.metadata.append('<em>{label}:</em> {media}'.
format(label=translate('SongsPlugin.MediaItem', 'Media'),
media=service_item.background_audio))
return True
def generate_footer(self, item, song):
@ -685,6 +697,23 @@ class SongMediaItem(MediaManagerItem):
if Settings().value('core/ccli number'):
item.raw_footer.append(translate('SongsPlugin.MediaItem',
'CCLI License: ') + Settings().value('core/ccli number'))
item.metadata.append('<em>{label}:</em> {title}'.format(label=translate('SongsPlugin.MediaItem', 'Title'),
title=song.title))
if song.alternate_title:
item.metadata.append('<em>{label}:</em> {title}'.
format(label=translate('SongsPlugin.MediaItem', 'Alt Title'),
title=song.alternate_title))
if song.songbook_entries:
for songbook_entry in song.songbook_entries:
item.metadata.append('<em>{label}:</em> {book}/{num}/{pub}'.
format(label=translate('SongsPlugin.MediaItem', 'Songbook'),
book=songbook_entry.songbook.name,
num=songbook_entry.entry,
pub=songbook_entry.songbook.publisher))
if song.topics:
for topics in song.topics:
item.metadata.append('<em>{label}:</em> {topic}'.
format(label=translate('SongsPlugin.MediaItem', 'Topic'), topic=topics.name))
return authors_all
def service_load(self, item):

View File

@ -51,6 +51,9 @@ class SongsTab(SettingsTab):
self.add_from_service_check_box = QtWidgets.QCheckBox(self.mode_group_box)
self.add_from_service_check_box.setObjectName('add_from_service_check_box')
self.mode_layout.addWidget(self.add_from_service_check_box)
self.songbook_slide_check_box = QtWidgets.QCheckBox(self.mode_group_box)
self.songbook_slide_check_box.setObjectName('songbook_slide_check_box')
self.mode_layout.addWidget(self.songbook_slide_check_box)
self.display_songbook_check_box = QtWidgets.QCheckBox(self.mode_group_box)
self.display_songbook_check_box.setObjectName('songbook_check_box')
self.mode_layout.addWidget(self.display_songbook_check_box)
@ -95,6 +98,7 @@ class SongsTab(SettingsTab):
self.tool_bar_active_check_box.stateChanged.connect(self.on_tool_bar_active_check_box_changed)
self.update_on_edit_check_box.stateChanged.connect(self.on_update_on_edit_check_box_changed)
self.add_from_service_check_box.stateChanged.connect(self.on_add_from_service_check_box_changed)
self.songbook_slide_check_box.stateChanged.connect(self.on_songbook_slide_check_box_changed)
self.display_songbook_check_box.stateChanged.connect(self.on_songbook_check_box_changed)
self.display_written_by_check_box.stateChanged.connect(self.on_written_by_check_box_changed)
self.display_copyright_check_box.stateChanged.connect(self.on_copyright_check_box_changed)
@ -111,6 +115,8 @@ class SongsTab(SettingsTab):
self.update_on_edit_check_box.setText(translate('SongsPlugin.SongsTab', 'Update service from song edit'))
self.add_from_service_check_box.setText(translate('SongsPlugin.SongsTab',
'Import missing songs from Service files'))
self.songbook_slide_check_box.setText(translate('SongsPlugin.SongsTab',
'Add Songbooks as first side'))
self.display_songbook_check_box.setText(translate('SongsPlugin.SongsTab', 'Display songbook in footer'))
self.display_written_by_check_box.setText(translate(
'SongsPlugin.SongsTab', 'Show "Written by:" in footer for unspecified authors'))
@ -141,6 +147,9 @@ class SongsTab(SettingsTab):
def on_add_from_service_check_box_changed(self, check_state):
self.update_load = (check_state == QtCore.Qt.Checked)
def on_songbook_slide_check_box_changed(self, check_state):
self.songbook_slide = (check_state == QtCore.Qt.Checked)
def on_songbook_check_box_changed(self, check_state):
self.display_songbook = (check_state == QtCore.Qt.Checked)
@ -171,6 +180,7 @@ class SongsTab(SettingsTab):
self.tool_bar = settings.value('display songbar')
self.update_edit = settings.value('update service on edit')
self.update_load = settings.value('add song from service')
self.songbook_slide = settings.value('add songbook slide')
self.display_songbook = settings.value('display songbook')
self.display_written_by = settings.value('display written by')
self.display_copyright_symbol = settings.value('display copyright symbol')
@ -208,6 +218,7 @@ class SongsTab(SettingsTab):
settings.setValue('mainview chords', self.mainview_chords)
settings.setValue('disable chords import', self.disable_chords_import)
settings.setValue('chord notation', self.chord_notation)
settings.setValue('add songbook slide', self.songbook_slide)
settings.endGroup()
if self.tab_visited:
self.settings_form.register_post_process('songs_config_updated')

View File

@ -61,6 +61,7 @@ __default_settings__ = {
'songs/last import type': SongFormat.OpenLyrics,
'songs/update service on edit': False,
'songs/add song from service': True,
'songs/add songbook slide': False,
'songs/display songbar': True,
'songs/display songbook': False,
'songs/display written by': True,

View File

@ -25,6 +25,7 @@ import asyncio
import websockets
import random
async def tester():
async with websockets.connect('ws://localhost:4317/poll') as websocket:

View File

@ -20,10 +20,26 @@
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
from unittest import TestCase
from unittest.mock import MagicMock, patch
from unittest.mock import MagicMock
from PyQt5 import QtCore
from openlp.core.common.registry import Registry
from openlp.core.api.endpoint.controller import controller_text
from openlp.core.api.endpoint.controller import controller_text, controller_direction
from openlp.core.display.renderer import Renderer
from openlp.core.display.screens import ScreenList
from openlp.core.lib import ServiceItem
from tests.utils import convert_file_service_item
from tests.utils.constants import RESOURCE_PATH
TEST_PATH = str(RESOURCE_PATH / 'service')
SCREEN = {
'primary': False,
'number': 1,
'size': QtCore.QRect(0, 0, 1024, 768)
}
class TestController(TestCase):
@ -38,11 +54,18 @@ class TestController(TestCase):
Registry.create()
self.registry = Registry()
self.mocked_live_controller = MagicMock()
self.desktop = MagicMock()
self.desktop.primaryScreen.return_value = SCREEN['primary']
self.desktop.screenCount.return_value = SCREEN['number']
self.desktop.screenGeometry.return_value = SCREEN['size']
self.screens = ScreenList.create(self.desktop)
renderer = Renderer()
renderer.empty_height = 1000
Registry().register('live_controller', self.mocked_live_controller)
def test_controller_text(self):
def test_controller_text_empty(self):
"""
Remote Deploy tests - test the dummy zip file is processed correctly
Remote API Tests : test the controller text method can be called with empty service item
"""
# GIVEN: A mocked service with a dummy service item
self.mocked_live_controller.service_item = MagicMock()
@ -52,3 +75,41 @@ class TestController(TestCase):
results = ret['results']
assert isinstance(results['item'], MagicMock)
assert len(results['slides']) == 0
def test_controller_text(self):
"""
Remote API Tests : test the controller text method can be called with a real service item
"""
# GIVEN: A mocked service with a dummy service item
line = convert_file_service_item(TEST_PATH, 'serviceitem_custom_1.osj')
self.mocked_live_controller.service_item = ServiceItem(None)
self.mocked_live_controller.service_item.set_from_service(line)
self.mocked_live_controller.service_item.render(True)
# WHEN: I trigger the method
ret = controller_text("SomeText")
# THEN: I get a basic set of results
results = ret['results']
assert isinstance(ret, dict)
assert len(results['slides']) == 2
def test_controller_direction_next(self):
"""
Text the live next method is triggered
"""
# GIVEN: A mocked service with a dummy service item
self.mocked_live_controller.service_item = MagicMock()
# WHEN: I trigger the method
controller_direction(None, 'live', 'next')
# THEN: The correct method is called
self.mocked_live_controller.slidecontroller_live_next.emit.assert_called_once_with()
def test_controller_direction_previous(self):
"""
Text the live next method is triggered
"""
# GIVEN: A mocked service with a dummy service item
self.mocked_live_controller.service_item = MagicMock()
# WHEN: I trigger the method
controller_direction(None, 'live', 'previous')
# THEN: The correct method is called
self.mocked_live_controller.slidecontroller_live_previous.emit.assert_called_once_with()

View File

@ -0,0 +1,151 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2018 OpenLP Developers #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
Functional tests to test the Http init.
"""
from unittest import TestCase
from unittest.mock import MagicMock
from openlp.core.api.http import check_auth, requires_auth, authenticate
from openlp.core.common.registry import Registry
from openlp.core.common.settings import Settings
from tests.helpers.testmixin import TestMixin
class TestInit(TestCase, TestMixin):
"""
A test suite to test the functions on the init
"""
def setUp(self):
"""
Create the UI
"""
Registry().create()
Registry().register('service_list', MagicMock())
self.build_settings()
self.password = 'c3VwZXJmbHk6bGFtYXM='
def tearDown(self):
self.destroy_settings()
def test_auth(self):
"""
Test the check_auth method with a match
:return:
"""
# GIVEN: a known user
Settings().setValue('api/user id', "superfly")
Settings().setValue('api/password', "lamas")
# WHEN : I check the authorisation
is_valid = check_auth(['aaaaa', self.password])
# THEN:
assert is_valid is True
def test_auth_falure(self):
"""
Test the check_auth method with a match
:return:
"""
# GIVEN: a known user
Settings().setValue('api/user id', 'superfly')
Settings().setValue('api/password', 'lamas')
# WHEN : I check the authorisation
is_valid = check_auth(['aaaaa', 'monkey123'])
# THEN:
assert is_valid is False
def test_requires_auth_disabled(self):
"""
Test the requires_auth wrapper with disabled security
:return:
"""
# GIVEN: A disabled security
Settings().setValue('api/authentication enabled', False)
# WHEN: I call the function
wrapped_function = requires_auth(func)
value = wrapped_function()
# THEN: the result will be as expected
assert value == 'called'
def test_requires_auth_enabled(self):
"""
Test the requires_auth wrapper with enabled security
:return:
"""
# GIVEN: A disabled security
Settings().setValue('api/authentication enabled', True)
# WHEN: I call the function
wrapped_function = requires_auth(func)
req = MagicMock()
value = wrapped_function(req)
# THEN: the result will be as expected
assert str(value) == str(authenticate())
def test_requires_auth_enabled_auth_error(self):
"""
Test the requires_auth wrapper with enabled security and authorization taken place and and error
:return:
"""
# GIVEN: A enabled security
Settings().setValue('api/authentication enabled', True)
# WHEN: I call the function with the wrong password
wrapped_function = requires_auth(func)
req = MagicMock()
req.authorization = ['Basic', 'cccccccc']
value = wrapped_function(req)
# THEN: the result will be as expected - try again
assert str(value) == str(authenticate())
def test_requires_auth_enabled_auth(self):
"""
Test the requires_auth wrapper with enabled security and authorization taken place and and error
:return:
"""
# GIVEN: An enabled security and a known user
Settings().setValue('api/authentication enabled', True)
Settings().setValue('api/user id', 'superfly')
Settings().setValue('api/password', 'lamas')
# WHEN: I call the function with the wrong password
wrapped_function = requires_auth(func)
req = MagicMock()
req.authorization = ['Basic', self.password]
value = wrapped_function(req)
# THEN: the result will be as expected - try again
assert str(value) == 'called'
def func(field=None):
return 'called'

View File

@ -41,7 +41,6 @@ def test_parse_options_basic():
args = parse_options()
# THEN: the following fields will have been extracted.
assert args.dev_version is False, 'The dev_version flag should be False'
assert args.loglevel == 'warning', 'The log level should be set to warning'
assert args.no_error_form is False, 'The no_error_form should be set to False'
assert args.portable is False, 'The portable flag should be set to false'
@ -59,7 +58,6 @@ def test_parse_options_debug():
args = parse_options()
# THEN: the following fields will have been extracted.
assert args.dev_version is False, 'The dev_version flag should be False'
assert args.loglevel == ' debug', 'The log level should be set to debug'
assert args.no_error_form is False, 'The no_error_form should be set to False'
assert args.portable is False, 'The portable flag should be set to false'
@ -77,7 +75,6 @@ def test_parse_options_debug_and_portable():
args = parse_options()
# THEN: the following fields will have been extracted.
assert args.dev_version is False, 'The dev_version flag should be False'
assert args.loglevel == 'warning', 'The log level should be set to warning'
assert args.no_error_form is False, 'The no_error_form should be set to False'
assert args.portable is True, 'The portable flag should be set to true'
@ -89,16 +86,15 @@ def test_parse_options_all_no_file():
Test the parse options process works with two options
"""
# GIVEN: a a set of system arguments.
sys.argv[1:] = ['-l debug', '-d']
sys.argv[1:] = ['-l debug', '-p']
# WHEN: We we parse them to expand to options
args = parse_options()
# THEN: the following fields will have been extracted.
assert args.dev_version is True, 'The dev_version flag should be True'
assert args.loglevel == ' debug', 'The log level should be set to debug'
assert args.no_error_form is False, 'The no_error_form should be set to False'
assert args.portable is False, 'The portable flag should be set to false'
assert args.portable is True, 'The portable flag should be set to false'
assert args.rargs == [], 'The service file should be blank'
@ -113,7 +109,6 @@ def test_parse_options_file():
args = parse_options()
# THEN: the following fields will have been extracted.
assert args.dev_version is False, 'The dev_version flag should be False'
assert args.loglevel == 'warning', 'The log level should be set to warning'
assert args.no_error_form is False, 'The no_error_form should be set to False'
assert args.portable is False, 'The portable flag should be set to false'
@ -131,7 +126,6 @@ def test_parse_options_file_and_debug():
args = parse_options()
# THEN: the following fields will have been extracted.
assert args.dev_version is False, 'The dev_version flag should be False'
assert args.loglevel == ' debug', 'The log level should be set to debug'
assert args.no_error_form is False, 'The no_error_form should be set to False'
assert args.portable is False, 'The portable flag should be set to false'

View File

@ -0,0 +1,114 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2018 OpenLP Developers #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
from unittest import TestCase
from unittest.mock import MagicMock, patch
from openlp.core.server import Server
from openlp.core.common.registry import Registry
from tests.helpers.testmixin import TestMixin
class TestServer(TestCase, TestMixin):
"""
Test the Server Class used to check if OpenLP is running.
"""
def setUp(self):
Registry.create()
with patch('PyQt5.QtNetwork.QLocalSocket'):
self.server = Server()
def tearDown(self):
self.server.close_server()
def test_is_another_instance_running(self):
"""
Run a test as if this was the first time and no instance is running
"""
# GIVEN: A running Server
# WHEN: I ask for it to start
value = self.server.is_another_instance_running()
# THEN the following is called
self.server.out_socket.waitForConnected.assert_called_once_with()
self.server.out_socket.connectToServer.assert_called_once_with(self.server.id)
assert isinstance(value, MagicMock)
def test_is_another_instance_running_true(self):
"""
Run a test as if there is another instance running
"""
# GIVEN: A running Server
self.server.out_socket.waitForConnected.return_value = True
# WHEN: I ask for it to start
value = self.server.is_another_instance_running()
# THEN the following is called
self.server.out_socket.waitForConnected.assert_called_once_with()
self.server.out_socket.connectToServer.assert_called_once_with(self.server.id)
assert value is True
def test_on_read_ready(self):
"""
Test the on_read_ready method calls the service_manager
"""
# GIVEN: A server with a service manager
self.server.in_stream = MagicMock()
service_manager = MagicMock()
Registry().register('service_manager', service_manager)
# WHEN: a file is added to the socket and the method called
file_name = '\\home\\superfly\\'
self.server.in_stream.readLine.return_value = file_name
self.server._on_ready_read()
# THEN: the service will be loaded
assert service_manager.on_load_service_clicked.call_count == 1
service_manager.on_load_service_clicked.assert_called_once_with(file_name)
@patch("PyQt5.QtCore.QTextStream")
def test_post_to_server(self, mocked_stream):
"""
A Basic test with a post to the service
:return:
"""
# GIVEN: A server
# WHEN: I post to a server
self.server.post_to_server(['l', 'a', 'm', 'a', 's'])
# THEN: the file should be passed out to the socket
self.server.out_socket.write.assert_called_once_with(b'lamas')
@patch("PyQt5.QtCore.QTextStream")
def test_post_to_server_openlp(self, mocked_stream):
"""
A Basic test with a post to the service with OpenLP
:return:
"""
# GIVEN: A server
# WHEN: I post to a server
self.server.post_to_server(['l', 'a', 'm', 'a', 's', 'OpenLP'])
# THEN: the file should be passed out to the socket
self.server.out_socket.write.assert_called_once_with(b'lamas')

View File

@ -431,14 +431,16 @@ class TestMediaItem(TestCase, TestMixin):
# GIVEN: A Song and a Service Item
song = Song()
song.title = 'My Song'
song.alternate_title = ''
song.copyright = 'My copyright'
song.authors_songs = []
song.songbook_entries = []
song.ccli_number = ''
song.topics = None
book1 = MagicMock()
book1.name = "My songbook"
book1.name = 'My songbook'
book2 = MagicMock()
book2.name = "Thy songbook"
book2.name = 'Thy songbook'
song.songbookentries = []
song.add_songbook_entry(book1, '12')
song.add_songbook_entry(book2, '502A')