forked from openlp/openlp
HEAD?
This commit is contained in:
commit
e1d2c67f33
@ -7,6 +7,7 @@ cover
|
||||
.coverage
|
||||
coverage
|
||||
.directory
|
||||
.vscode
|
||||
dist
|
||||
*.dll
|
||||
documentation/build/doctrees
|
||||
|
@ -706,6 +706,7 @@ class Renderer(RegistryBase, LogMixin, RegistryProperties, DisplayWindow):
|
||||
else:
|
||||
# The remaining elements do not fit, thus reset the indexes, create a new list and continue.
|
||||
raw_list = raw_list[index + 1:]
|
||||
log.debug(raw_list)
|
||||
raw_list[0] = raw_tags + raw_list[0]
|
||||
html_list = html_list[index + 1:]
|
||||
html_list[0] = html_tags + html_list[0]
|
||||
|
@ -29,16 +29,14 @@ import copy
|
||||
|
||||
from PyQt5 import QtCore, QtWebChannel, QtWidgets
|
||||
|
||||
from openlp.core.common.path import Path, path_to_str
|
||||
from openlp.core.common.path import path_to_str
|
||||
from openlp.core.common.settings import Settings
|
||||
from openlp.core.common.registry import Registry
|
||||
from openlp.core.common.applocation import AppLocation
|
||||
from openlp.core.ui import HideMode
|
||||
from openlp.core.display.screens import ScreenList
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
DISPLAY_PATH = Path(__file__).parent / 'html' / 'display.html'
|
||||
CHECKERBOARD_PATH = Path(__file__).parent / 'html' / 'checkerboard.png'
|
||||
OPENLP_SPLASH_SCREEN_PATH = Path(__file__).parent / 'html' / 'openlp-splash-screen.png'
|
||||
|
||||
|
||||
class MediaWatcher(QtCore.QObject):
|
||||
@ -126,7 +124,11 @@ class DisplayWindow(QtWidgets.QWidget):
|
||||
self.webview.page().setBackgroundColor(QtCore.Qt.transparent)
|
||||
self.layout.addWidget(self.webview)
|
||||
self.webview.loadFinished.connect(self.after_loaded)
|
||||
self.set_url(QtCore.QUrl.fromLocalFile(path_to_str(DISPLAY_PATH)))
|
||||
display_base_path = AppLocation.get_directory(AppLocation.AppDir) / 'core' / 'display' / 'html'
|
||||
self.display_path = display_base_path / 'display.html'
|
||||
self.checkerboard_path = display_base_path / 'checkerboard.png'
|
||||
self.openlp_splash_screen_path = display_base_path / 'openlp-splash-screen.png'
|
||||
self.set_url(QtCore.QUrl.fromLocalFile(path_to_str(self.display_path)))
|
||||
self.media_watcher = MediaWatcher(self)
|
||||
self.channel = QtWebChannel.QWebChannel(self)
|
||||
self.channel.registerObject('mediaWatcher', self.media_watcher)
|
||||
@ -169,7 +171,7 @@ class DisplayWindow(QtWidgets.QWidget):
|
||||
bg_color = Settings().value('core/logo background color')
|
||||
image = Settings().value('core/logo file')
|
||||
if path_to_str(image).startswith(':'):
|
||||
image = OPENLP_SPLASH_SCREEN_PATH
|
||||
image = self.openlp_splash_screen_path
|
||||
image_uri = image.as_uri()
|
||||
self.run_javascript('Display.setStartupSplashScreen("{bg_color}", "{image}");'.format(bg_color=bg_color,
|
||||
image=image_uri))
|
||||
@ -329,7 +331,7 @@ class DisplayWindow(QtWidgets.QWidget):
|
||||
if theme.background_type == 'transparent' and not self.is_display:
|
||||
theme_copy = copy.deepcopy(theme)
|
||||
theme_copy.background_type = 'image'
|
||||
theme_copy.background_filename = CHECKERBOARD_PATH
|
||||
theme_copy.background_filename = self.checkerboard_path
|
||||
exported_theme = theme_copy.export_theme()
|
||||
else:
|
||||
exported_theme = theme.export_theme()
|
||||
|
@ -417,11 +417,17 @@ class ProjectorDB(Manager):
|
||||
value: (str) From ProjectorSource, Sources tables or PJLink default code list
|
||||
"""
|
||||
source_dict = {}
|
||||
# Apparently, there was a change to the projector object. Test for which object has db id
|
||||
if hasattr(projector, "id"):
|
||||
chk = projector.id
|
||||
elif hasattr(projector.entry, "id"):
|
||||
chk = projector.entry.id
|
||||
|
||||
# Get default list first
|
||||
for key in projector.source_available:
|
||||
item = self.get_object_filtered(ProjectorSource,
|
||||
and_(ProjectorSource.code == key,
|
||||
ProjectorSource.projector_id == projector.id))
|
||||
ProjectorSource.projector_id == chk))
|
||||
if item is None:
|
||||
source_dict[key] = PJLINK_DEFAULT_CODES[key]
|
||||
else:
|
||||
|
@ -46,31 +46,10 @@ from openlp.core.projectors.sourceselectform import SourceSelectSingle, SourceSe
|
||||
from openlp.core.ui.icons import UiIcons
|
||||
from openlp.core.widgets.toolbar import OpenLPToolbar
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
log.debug('projectormanager loaded')
|
||||
|
||||
|
||||
# Dict for matching projector status to display icon
|
||||
STATUS_ICONS = {
|
||||
S_NOT_CONNECTED: ':/projector/projector_item_disconnect.png',
|
||||
S_CONNECTING: ':/projector/projector_item_connect.png',
|
||||
S_CONNECTED: ':/projector/projector_off.png',
|
||||
S_OFF: ':/projector/projector_off.png',
|
||||
S_INITIALIZE: ':/projector/projector_off.png',
|
||||
S_STANDBY: ':/projector/projector_off.png',
|
||||
S_WARMUP: ':/projector/projector_warmup.png',
|
||||
S_ON: ':/projector/projector_on.png',
|
||||
S_COOLDOWN: ':/projector/projector_cooldown.png',
|
||||
E_ERROR: ':/projector/projector_error.png',
|
||||
E_NETWORK: ':/projector/projector_not_connected_error.png',
|
||||
E_SOCKET_TIMEOUT: ':/projector/projector_not_connected_error.png',
|
||||
E_AUTHENTICATION: ':/projector/projector_not_connected_error.png',
|
||||
E_UNKNOWN_SOCKET_ERROR: ':/projector/projector_not_connected_error.png',
|
||||
E_NOT_CONNECTED: ':/projector/projector_not_connected_error.png'
|
||||
}
|
||||
|
||||
|
||||
class UiProjectorManager(object):
|
||||
"""
|
||||
UI part of the Projector Manager
|
||||
@ -122,7 +101,7 @@ class UiProjectorManager(object):
|
||||
self.one_toolbar.add_toolbar_action('connect_projector',
|
||||
text=translate('OpenLP.ProjectorManager',
|
||||
'Connect to selected projector.'),
|
||||
icon=UiIcons().projector_connect,
|
||||
icon=UiIcons().projector_select_connect,
|
||||
tooltip=translate('OpenLP.ProjectorManager',
|
||||
'Connect to selected projector.'),
|
||||
triggers=self.on_connect_projector)
|
||||
@ -136,7 +115,7 @@ class UiProjectorManager(object):
|
||||
self.one_toolbar.add_toolbar_action('disconnect_projector',
|
||||
text=translate('OpenLP.ProjectorManager',
|
||||
'Disconnect from selected projectors'),
|
||||
icon=UiIcons().projector_disconnect,
|
||||
icon=UiIcons().projector_select_disconnect,
|
||||
tooltip=translate('OpenLP.ProjectorManager',
|
||||
'Disconnect from selected projector.'),
|
||||
triggers=self.on_disconnect_projector)
|
||||
@ -151,7 +130,7 @@ class UiProjectorManager(object):
|
||||
self.one_toolbar.add_toolbar_action('poweron_projector',
|
||||
text=translate('OpenLP.ProjectorManager',
|
||||
'Power on selected projector'),
|
||||
icon=UiIcons().projector_on,
|
||||
icon=UiIcons().projector_power_on,
|
||||
tooltip=translate('OpenLP.ProjectorManager',
|
||||
'Power on selected projector.'),
|
||||
triggers=self.on_poweron_projector)
|
||||
@ -164,7 +143,7 @@ class UiProjectorManager(object):
|
||||
triggers=self.on_poweron_projector)
|
||||
self.one_toolbar.add_toolbar_action('poweroff_projector',
|
||||
text=translate('OpenLP.ProjectorManager', 'Standby selected projector'),
|
||||
icon=UiIcons().projector_off,
|
||||
icon=UiIcons().projector_power_off,
|
||||
tooltip=translate('OpenLP.ProjectorManager',
|
||||
'Put selected projector in standby.'),
|
||||
triggers=self.on_poweroff_projector)
|
||||
@ -309,7 +288,7 @@ class ProjectorManager(QtWidgets.QWidget, RegistryBase, UiProjectorManager, LogM
|
||||
S_INITIALIZE: UiIcons().projector_on,
|
||||
S_STANDBY: UiIcons().projector_off,
|
||||
S_WARMUP: UiIcons().projector_warmup,
|
||||
S_ON: UiIcons().projector_off,
|
||||
S_ON: UiIcons().projector_on,
|
||||
S_COOLDOWN: UiIcons().projector_cooldown,
|
||||
E_ERROR: UiIcons().projector_error,
|
||||
E_NETWORK: UiIcons().error,
|
||||
@ -880,6 +859,7 @@ class ProjectorManager(QtWidgets.QWidget, RegistryBase, UiProjectorManager, LogM
|
||||
"""
|
||||
Update the icons when the selected projectors change
|
||||
"""
|
||||
log.debug('update_icons(): Checking for selected projector items in list')
|
||||
count = len(self.projector_list_widget.selectedItems())
|
||||
projector = None
|
||||
if count == 0:
|
||||
@ -900,6 +880,7 @@ class ProjectorManager(QtWidgets.QWidget, RegistryBase, UiProjectorManager, LogM
|
||||
self.get_toolbar_item('blank_projector_multiple', hidden=True)
|
||||
self.get_toolbar_item('show_projector_multiple', hidden=True)
|
||||
elif count == 1:
|
||||
log.debug('update_icons(): Found one item selected')
|
||||
projector = self.projector_list_widget.selectedItems()[0].data(QtCore.Qt.UserRole)
|
||||
connected = QSOCKET_STATE[projector.link.state()] == S_CONNECTED
|
||||
power = projector.link.power == S_ON
|
||||
@ -910,12 +891,14 @@ class ProjectorManager(QtWidgets.QWidget, RegistryBase, UiProjectorManager, LogM
|
||||
self.get_toolbar_item('blank_projector_multiple', hidden=True)
|
||||
self.get_toolbar_item('show_projector_multiple', hidden=True)
|
||||
if connected:
|
||||
log.debug('update_icons(): Updating icons for connected state')
|
||||
self.get_toolbar_item('view_projector', enabled=True)
|
||||
self.get_toolbar_item('source_view_projector',
|
||||
enabled=connected and power and projector.link.source_available is not None)
|
||||
enabled=projector.link.source_available is not None and connected and power)
|
||||
self.get_toolbar_item('edit_projector', hidden=True)
|
||||
self.get_toolbar_item('delete_projector', hidden=True)
|
||||
else:
|
||||
log.debug('update_icons(): Updating for not connected state')
|
||||
self.get_toolbar_item('view_projector', hidden=True)
|
||||
self.get_toolbar_item('source_view_projector', hidden=True)
|
||||
self.get_toolbar_item('edit_projector', enabled=True)
|
||||
@ -931,6 +914,7 @@ class ProjectorManager(QtWidgets.QWidget, RegistryBase, UiProjectorManager, LogM
|
||||
self.get_toolbar_item('blank_projector', enabled=False)
|
||||
self.get_toolbar_item('show_projector', enabled=False)
|
||||
else:
|
||||
log.debug('update_icons(): Updating for multiple items selected')
|
||||
self.get_toolbar_item('edit_projector', enabled=False)
|
||||
self.get_toolbar_item('delete_projector', enabled=False)
|
||||
self.get_toolbar_item('view_projector', hidden=True)
|
||||
|
@ -528,8 +528,9 @@ class PJLinkCommands(object):
|
||||
sources.append(source)
|
||||
sources.sort()
|
||||
self.source_available = sources
|
||||
log.debug('({ip}) Setting projector sources_available to "{data}"'.format(ip=self.entry.name,
|
||||
data=self.source_available))
|
||||
log.debug('({ip}) Setting projector source_available to "{data}"'.format(ip=self.entry.name,
|
||||
data=self.source_available))
|
||||
self.projectorUpdateIcons.emit()
|
||||
return
|
||||
|
||||
def process_lamp(self, data):
|
||||
@ -886,6 +887,9 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands):
|
||||
elif status >= S_NOT_CONNECTED and status in QSOCKET_STATE:
|
||||
# Socket connection status update
|
||||
self.status_connect = status
|
||||
# Check if we need to update error state as well
|
||||
if self.error_status != S_OK and status != S_NOT_CONNECTED:
|
||||
self.error_status = S_OK
|
||||
elif status >= S_NOT_CONNECTED and status in PROJECTOR_STATE:
|
||||
# Only affects the projector status
|
||||
self.projector_status = status
|
||||
@ -905,7 +909,14 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands):
|
||||
message=status_message if msg is None else msg))
|
||||
|
||||
# Now that we logged extra information for debugging, broadcast the original change/message
|
||||
(code, message) = self._get_status(status)
|
||||
# Check for connection errors first
|
||||
if self.error_status != S_OK:
|
||||
log.debug('({ip}) Signalling error code'.format(ip=self.entry.name))
|
||||
code, message = self._get_status(self.error_status)
|
||||
status = self.error_status
|
||||
else:
|
||||
log.debug('({ip}) Signalling status code'.format(ip=self.entry.name))
|
||||
code, message = self._get_status(status)
|
||||
if msg is not None:
|
||||
message = msg
|
||||
elif message is None:
|
||||
@ -953,7 +964,7 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands):
|
||||
log.error('({ip}) Invalid initial packet received - closing socket'.format(ip=self.entry.name))
|
||||
return self.disconnect_from_host()
|
||||
# Convert the initial login prompt with the expected PJLink normal command format for processing
|
||||
log.debug('({ip}) check_login(): Formatting initial connection prompt'
|
||||
log.debug('({ip}) check_login(): Formatting initial connection prompt '
|
||||
'to PJLink packet'.format(ip=self.entry.name))
|
||||
return self.get_data('{start}{clss}{data}'.format(start=PJLINK_PREFIX,
|
||||
clss='1',
|
||||
|
@ -455,7 +455,7 @@ class AdvancedTab(SettingsTab):
|
||||
Service Name options changed
|
||||
"""
|
||||
self.service_name_day.setEnabled(default_service_enabled)
|
||||
time_enabled = default_service_enabled and self.service_name_day.currentIndex() is not 7
|
||||
time_enabled = default_service_enabled and self.service_name_day.currentIndex() != 7
|
||||
self.service_name_time.setEnabled(time_enabled)
|
||||
self.service_name_edit.setEnabled(default_service_enabled)
|
||||
self.service_name_revert_button.setEnabled(default_service_enabled)
|
||||
@ -497,7 +497,7 @@ class AdvancedTab(SettingsTab):
|
||||
"""
|
||||
React to the day of the service name changing.
|
||||
"""
|
||||
self.service_name_time.setEnabled(service_day is not 7)
|
||||
self.service_name_time.setEnabled(service_day != 7)
|
||||
self.update_service_name_example(None)
|
||||
|
||||
def on_service_name_revert_button_clicked(self):
|
||||
|
@ -117,13 +117,17 @@ class UiIcons(object):
|
||||
'presentation': {'icon': 'fa.bar-chart'},
|
||||
'preview': {'icon': 'fa.laptop'},
|
||||
'projector': {'icon': 'op.video'},
|
||||
'projector_connect': {'icon': 'fa.plug'},
|
||||
'projector_connect': {'icon': 'fa.plug'}, # Projector connect
|
||||
'projector_cooldown': {'icon': 'fa.video-camera', 'attr': 'blue'},
|
||||
'projector_disconnect': {'icon': 'fa.plug', 'attr': 'lightGray'},
|
||||
'projector_disconnect': {'icon': 'fa.plug', 'attr': 'lightGray'}, # Projector disconnect
|
||||
'projector_error': {'icon': 'fa.video-camera', 'attr': 'red'},
|
||||
'projector_hdmi': {'icon': 'op.hdmi'},
|
||||
'projector_off': {'icon': 'fa.video-camera', 'attr': 'black'},
|
||||
'projector_on': {'icon': 'fa.video-camera', 'attr': 'green'},
|
||||
'projector_power_off': {'icon': 'fa.video-camera', 'attr': 'red'}, # Toolbar power off
|
||||
'projector_power_on': {'icon': 'fa.video-camera', 'attr': 'green'}, # Toolbar power on
|
||||
'projector_off': {'icon': 'fa.video-camera', 'attr': 'black'}, # Projector off
|
||||
'projector_on': {'icon': 'fa.video-camera', 'attr': 'green'}, # Projector on
|
||||
'projector_select_connect': {'icon': 'fa.plug', 'attr': 'green'}, # Toolbar connect
|
||||
'projector_select_disconnect': {'icon': 'fa.plug', 'attr': 'red'}, # Toolbar disconnect
|
||||
'projector_warmup': {'icon': 'fa.video-camera', 'attr': 'yellow'},
|
||||
'picture': {'icon': 'fa.picture-o'},
|
||||
'print': {'icon': 'fa.print'},
|
||||
|
@ -206,7 +206,7 @@ class ThemesTab(SettingsTab):
|
||||
find_and_set_in_combo_box(self.default_combo_box, self.global_theme)
|
||||
# self.renderer.set_global_theme()
|
||||
self.renderer.set_theme_level(self.theme_level)
|
||||
if self.global_theme is not '':
|
||||
if self.global_theme != '':
|
||||
self._preview_global_theme()
|
||||
|
||||
def _preview_global_theme(self):
|
||||
|
@ -46,6 +46,7 @@ from openlp.plugins.presentations.lib.presentationcontroller import Presentation
|
||||
if is_win():
|
||||
from win32com.client import Dispatch
|
||||
import pywintypes
|
||||
uno_available = False
|
||||
# Declare an empty exception to match the exception imported from UNO
|
||||
|
||||
class ErrorCodeIOException(Exception):
|
||||
|
@ -263,8 +263,9 @@ class SongSelectForm(QtWidgets.QDialog, Ui_SongSelectDialog, RegistryProperties)
|
||||
self.login_progress_bar.setVisible(True)
|
||||
self.application.process_events()
|
||||
# Log the user in
|
||||
if not self.song_select_importer.login(
|
||||
self.username_edit.text(), self.password_edit.text(), self._update_login_progress):
|
||||
subscription_level = self.song_select_importer.login(
|
||||
self.username_edit.text(), self.password_edit.text(), self._update_login_progress)
|
||||
if not subscription_level:
|
||||
QtWidgets.QMessageBox.critical(
|
||||
self,
|
||||
translate('SongsPlugin.SongSelectForm', 'Error Logging In'),
|
||||
@ -272,6 +273,14 @@ class SongSelectForm(QtWidgets.QDialog, Ui_SongSelectDialog, RegistryProperties)
|
||||
'There was a problem logging in, perhaps your username or password is incorrect?')
|
||||
)
|
||||
else:
|
||||
if subscription_level == 'Free':
|
||||
QtWidgets.QMessageBox.information(
|
||||
self,
|
||||
translate('SongsPlugin.SongSelectForm', 'Free user'),
|
||||
translate('SongsPlugin.SongSelectForm', 'You logged in with a free account, '
|
||||
'the search will be limited to songs '
|
||||
'in the public domain.')
|
||||
)
|
||||
if self.save_password_checkbox.isChecked():
|
||||
Settings().setValue(self.plugin.settings_section + '/songselect username', self.username_edit.text())
|
||||
Settings().setValue(self.plugin.settings_section + '/songselect password', self.password_edit.text())
|
||||
|
@ -81,7 +81,7 @@ class SongSelectImport(object):
|
||||
:param username: SongSelect username
|
||||
:param password: SongSelect password
|
||||
:param callback: Method to notify of progress.
|
||||
:return: True on success, False on failure.
|
||||
:return: subscription level on success, None on failure.
|
||||
"""
|
||||
if callback:
|
||||
callback()
|
||||
@ -115,11 +115,31 @@ class SongSelectImport(object):
|
||||
return False
|
||||
if callback:
|
||||
callback()
|
||||
# Page if user is in an organization
|
||||
if posted_page.find('input', id='SearchText') is not None:
|
||||
return True
|
||||
self.subscription_level = self.find_subscription_level(posted_page)
|
||||
return self.subscription_level
|
||||
# Page if user is not in an organization
|
||||
elif posted_page.find('div', id="select-organization") is not None:
|
||||
try:
|
||||
home_page = BeautifulSoup(self.opener.open(BASE_URL).read(), 'lxml')
|
||||
except (TypeError, URLError) as error:
|
||||
log.exception('Could not reach SongSelect, {error}'.format(error=error))
|
||||
self.subscription_level = self.find_subscription_level(home_page)
|
||||
return self.subscription_level
|
||||
else:
|
||||
log.debug(posted_page)
|
||||
return False
|
||||
return None
|
||||
|
||||
def find_subscription_level(self, page):
|
||||
scripts = page.find_all('script')
|
||||
for tag in scripts:
|
||||
if tag.string:
|
||||
match = re.search("'Subscription': '(?P<subscription_level>[^']+)", tag.string)
|
||||
if match:
|
||||
return match.group('subscription_level')
|
||||
log.error('Could not determine SongSelect subscription level')
|
||||
return None
|
||||
|
||||
def logout(self):
|
||||
"""
|
||||
@ -128,7 +148,7 @@ class SongSelectImport(object):
|
||||
try:
|
||||
self.opener.open(LOGOUT_URL)
|
||||
except (TypeError, URLError) as error:
|
||||
log.exception('Could not log of SongSelect, {error}'.format(error=error))
|
||||
log.exception('Could not log out of SongSelect, {error}'.format(error=error))
|
||||
|
||||
def search(self, search_text, max_results, callback=None):
|
||||
"""
|
||||
@ -145,7 +165,7 @@ class SongSelectImport(object):
|
||||
'PrimaryLanguage': '',
|
||||
'Keys': '',
|
||||
'Themes': '',
|
||||
'List': '',
|
||||
'List': 'publicdomain' if self.subscription_level == 'Free' else '',
|
||||
'Sort': '',
|
||||
'SearchText': search_text
|
||||
}
|
||||
|
@ -121,7 +121,7 @@ class SongsPlugin(Plugin):
|
||||
self.song_export_item.setVisible(True)
|
||||
self.song_tools_menu.menuAction().setVisible(True)
|
||||
action_list = ActionList.get_instance()
|
||||
action_list.add_action(self.song_import_item, UiStrings().Import)
|
||||
#action_list.add_action(self.song_import_item, UiStrings().Import)
|
||||
action_list.add_action(self.song_export_item, UiStrings().Export)
|
||||
action_list.add_action(self.tools_reindex_item, UiStrings().Tools)
|
||||
action_list.add_action(self.tools_find_duplicates, UiStrings().Tools)
|
||||
|
@ -1,5 +1,8 @@
|
||||
version: OpenLP-win-ci-b{build}
|
||||
|
||||
image:
|
||||
- Visual Studio 2017
|
||||
|
||||
clone_script:
|
||||
- curl -L https://bazaar.launchpad.net/BRANCHPATH/tarball -o sourcecode.tar.gz
|
||||
- 7z e sourcecode.tar.gz
|
||||
@ -7,15 +10,17 @@ clone_script:
|
||||
- mv BRANCHPATH openlp-branch
|
||||
|
||||
environment:
|
||||
PYTHON: C:\\Python37-x64
|
||||
matrix:
|
||||
- PYTHON: C:\\Python37-x64
|
||||
- PYTHON: C:\\Python37
|
||||
|
||||
install:
|
||||
# Install dependencies from pypi
|
||||
- "%PYTHON%\\python.exe -m pip install sqlalchemy alembic appdirs chardet beautifulsoup4 lxml Mako mysql-connector-python nose mock pyodbc psycopg2 pypiwin32 websockets asyncio waitress six webob requests QtAwesome PyQt5 pymediainfo"
|
||||
- "%PYTHON%\\python.exe -m pip install sqlalchemy alembic appdirs chardet beautifulsoup4 lxml Mako mysql-connector-python pytest mock pyodbc psycopg2 pypiwin32 websockets asyncio waitress six webob requests QtAwesome PyQt5 PyQtWebEngine pymediainfo"
|
||||
# Download and unpack mupdf
|
||||
- appveyor DownloadFile https://mupdf.com/downloads/archive/mupdf-1.14.0-windows.zip
|
||||
- 7z x mupdf-1.14.0-windows.zip
|
||||
- cp mupdf-1.14.0-windows/mupdf.exe openlp-branch/mupdf.exe
|
||||
- cp mupdf-1.14.0-windows/mutool.exe openlp-branch/mutool.exe
|
||||
# Download and unpack mediainfo
|
||||
- appveyor DownloadFile https://mediaarea.net/download/binary/mediainfo/18.08.1/MediaInfo_CLI_18.08.1_Windows_i386.zip
|
||||
- mkdir MediaInfo
|
||||
@ -27,7 +32,7 @@ build: off
|
||||
test_script:
|
||||
- cd openlp-branch
|
||||
# Run the tests
|
||||
- "%PYTHON%\\python.exe -m nose -v tests"
|
||||
- "%PYTHON%\\python.exe -m pytest -v tests"
|
||||
# Go back to the user root folder
|
||||
- cd..
|
||||
|
||||
|
@ -9,6 +9,7 @@ ignore = E402,E722,W503,W504
|
||||
|
||||
[flake8]
|
||||
exclude=resources.py,vlc.py
|
||||
max-line-length = 120
|
||||
ignore = E402,W503,W504,D
|
||||
|
||||
[pycodestyle]
|
||||
|
@ -15,13 +15,12 @@
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
|
||||
# more details. #
|
||||
# #
|
||||
|
||||
|
||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||
###############################################################################
|
||||
from tempfile import mkdtemp
|
||||
# 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 #
|
||||
###############################################################################
|
||||
import os
|
||||
from tempfile import mkdtemp
|
||||
from unittest import TestCase
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
@ -57,7 +56,8 @@ class TestRemoteDeploy(TestCase):
|
||||
# GIVEN: A new downloaded zip file
|
||||
mocked_zipfile = MagicMock()
|
||||
MockZipFile.return_value = mocked_zipfile
|
||||
root_path = Path('/tmp/remotes')
|
||||
root_path_str = '{sep}tmp{sep}remotes'.format(sep=os.sep)
|
||||
root_path = Path(root_path_str)
|
||||
|
||||
# WHEN: deploy_zipfile() is called
|
||||
deploy_zipfile(root_path, 'site.zip')
|
||||
|
@ -322,7 +322,7 @@ class TestPath(TestCase):
|
||||
obj = path.json_object(extra=1, args=2)
|
||||
|
||||
# THEN: A JSON decodable object should have been returned.
|
||||
assert obj == {'__Path__': ('/', 'base', 'path', 'to', 'fi.le')}
|
||||
assert obj == {'__Path__': (os.sep, 'base', 'path', 'to', 'fi.le')}
|
||||
|
||||
def test_path_json_object_base_path(self):
|
||||
"""
|
||||
|
@ -178,7 +178,7 @@ class TestImageManager(TestCase, TestMixin):
|
||||
# THEN a KeyError is thrown
|
||||
with self.assertRaises(KeyError) as context:
|
||||
self.image_manager.get_image(TEST_PATH, 'church1.jpg')
|
||||
assert context.exception is not '', 'KeyError exception should have been thrown for missing image'
|
||||
assert context.exception != '', 'KeyError exception should have been thrown for missing image'
|
||||
|
||||
@patch('openlp.core.lib.imagemanager.run_thread')
|
||||
def test_different_dimension_image(self, mocked_run_thread):
|
||||
@ -211,7 +211,7 @@ class TestImageManager(TestCase, TestMixin):
|
||||
# WHEN: calling with correct image, but wrong dimensions
|
||||
with self.assertRaises(KeyError) as context:
|
||||
self.image_manager.get_image(full_path, 'church.jpg', 120, 120)
|
||||
assert context.exception is not '', 'KeyError exception should have been thrown for missing dimension'
|
||||
assert context.exception != '', 'KeyError exception should have been thrown for missing dimension'
|
||||
|
||||
@patch('openlp.core.lib.imagemanager.resize_image')
|
||||
@patch('openlp.core.lib.imagemanager.image_to_byte')
|
||||
|
@ -154,13 +154,13 @@ class TestServiceItem(TestCase, TestMixin):
|
||||
mocked_get_section_data_path:
|
||||
mocked_exists.return_value = True
|
||||
mocked_get_section_data_path.return_value = Path('/path/')
|
||||
service_item.set_from_service(line, TEST_PATHb)
|
||||
service_item.set_from_service(line, TEST_PATH)
|
||||
|
||||
# THEN: We should get back a valid service item
|
||||
assert service_item.is_valid is True, 'The new service item should be valid'
|
||||
assert test_file == service_item.get_rendered_frame(0), 'The first frame should match the path to the image'
|
||||
assert frame_array == service_item.get_frames()[0], 'The return should match frame array1'
|
||||
assert test_file == str(service_item.get_frame_path(0)), \
|
||||
assert test_file == service_item.get_frame_path(0), \
|
||||
'The frame path should match the full path to the image'
|
||||
assert image_name == service_item.get_frame_title(0), 'The frame title should match the image name'
|
||||
assert image_name == service_item.get_display_title(), 'The display title should match the first image name'
|
||||
@ -328,7 +328,7 @@ class TestServiceItem(TestCase, TestMixin):
|
||||
|
||||
# WHEN: We add a custom from a saved service
|
||||
line = convert_file_service_item(TEST_PATH, 'serviceitem-song-linked-audio.osj')
|
||||
service_item.set_from_service(line, '/test/')
|
||||
service_item.set_from_service(line, Path('/test/'))
|
||||
|
||||
# THEN: We should get back a valid service item
|
||||
assert service_item.is_valid is True, 'The new service item should be valid'
|
||||
|
@ -57,4 +57,4 @@ class MediaPluginTest(TestCase, TestMixin):
|
||||
# THEN: about() should return a string object
|
||||
assert isinstance(MediaPlugin.about(), str)
|
||||
# THEN: about() should return a non-empty string
|
||||
assert len(MediaPlugin.about()) is not 0
|
||||
assert len(MediaPlugin.about()) != 0
|
||||
|
@ -199,7 +199,7 @@ class TestLib(TestCase):
|
||||
result = _remove_typos(diff)
|
||||
|
||||
# THEN: There should be no typos in the middle anymore. The remaining equals should have been merged.
|
||||
assert len(result) is 1, 'The result should contain only one element.'
|
||||
assert len(result) == 1, 'The result should contain only one element.'
|
||||
assert result[0][0] == 'equal', 'The result should contain an equal element.'
|
||||
assert result[0][1] == 0, 'The start indices should be kept.'
|
||||
assert result[0][2] == 22, 'The stop indices should be kept.'
|
||||
|
@ -64,7 +64,7 @@ class TestSongSelectImport(TestCase, TestMixin):
|
||||
@patch('openlp.plugins.songs.lib.songselect.BeautifulSoup')
|
||||
def test_login_fails(self, MockedBeautifulSoup, mocked_build_opener):
|
||||
"""
|
||||
Test that when logging in to SongSelect fails, the login method returns False
|
||||
Test that when logging in to SongSelect fails, the login method returns None
|
||||
"""
|
||||
# GIVEN: A bunch of mocked out stuff and an importer object
|
||||
mocked_opener = MagicMock()
|
||||
@ -80,12 +80,12 @@ class TestSongSelectImport(TestCase, TestMixin):
|
||||
# WHEN: The login method is called after being rigged to fail
|
||||
result = importer.login('username', 'password', mock_callback)
|
||||
|
||||
# THEN: callback was called 3 times, open was called twice, find was called twice, and False was returned
|
||||
# THEN: callback was called 3 times, open was called twice, find was called twice, and None was returned
|
||||
assert 3 == mock_callback.call_count, 'callback should have been called 3 times'
|
||||
assert 2 == mocked_login_page.find.call_count, 'find should have been called twice'
|
||||
assert 1 == mocked_posted_page.find.call_count, 'find should have been called once'
|
||||
assert 2 == mocked_posted_page.find.call_count, 'find should have been called twice'
|
||||
assert 2 == mocked_opener.open.call_count, 'opener should have been called twice'
|
||||
assert result is False, 'The login method should have returned False'
|
||||
assert result is None, 'The login method should have returned None'
|
||||
|
||||
@patch('openlp.plugins.songs.lib.songselect.build_opener')
|
||||
def test_login_except(self, mocked_build_opener):
|
||||
@ -129,7 +129,7 @@ class TestSongSelectImport(TestCase, TestMixin):
|
||||
assert 2 == mocked_login_page.find.call_count, 'find should have been called twice on the login page'
|
||||
assert 1 == mocked_posted_page.find.call_count, 'find should have been called once on the posted page'
|
||||
assert 2 == mocked_opener.open.call_count, 'opener should have been called twice'
|
||||
assert result is True, 'The login method should have returned True'
|
||||
assert result is None, 'The login method should have returned the subscription level'
|
||||
|
||||
@patch('openlp.plugins.songs.lib.songselect.build_opener')
|
||||
@patch('openlp.plugins.songs.lib.songselect.BeautifulSoup')
|
||||
@ -146,7 +146,8 @@ class TestSongSelectImport(TestCase, TestMixin):
|
||||
mocked_login_page.find.side_effect = [{'value': 'blah'}, mocked_form]
|
||||
mocked_posted_page = MagicMock()
|
||||
mocked_posted_page.find.return_value = MagicMock()
|
||||
MockedBeautifulSoup.side_effect = [mocked_login_page, mocked_posted_page]
|
||||
mocked_home_page = MagicMock()
|
||||
MockedBeautifulSoup.side_effect = [mocked_login_page, mocked_posted_page, mocked_home_page]
|
||||
mock_callback = MagicMock()
|
||||
importer = SongSelectImport(None)
|
||||
|
||||
@ -158,7 +159,7 @@ class TestSongSelectImport(TestCase, TestMixin):
|
||||
assert 2 == mocked_login_page.find.call_count, 'find should have been called twice on the login page'
|
||||
assert 1 == mocked_posted_page.find.call_count, 'find should have been called once on the posted page'
|
||||
assert 'https://profile.ccli.com/do/login', mocked_opener.open.call_args_list[1][0][0]
|
||||
assert result is True, 'The login method should have returned True'
|
||||
assert result is None, 'The login method should have returned the subscription level'
|
||||
|
||||
@patch('openlp.plugins.songs.lib.songselect.build_opener')
|
||||
def test_logout(self, mocked_build_opener):
|
||||
@ -191,6 +192,7 @@ class TestSongSelectImport(TestCase, TestMixin):
|
||||
MockedBeautifulSoup.return_value = mocked_results_page
|
||||
mock_callback = MagicMock()
|
||||
importer = SongSelectImport(None)
|
||||
importer.subscription_level = 'premium'
|
||||
|
||||
# WHEN: The login method is called after being rigged to fail
|
||||
results = importer.search('text', 1000, mock_callback)
|
||||
@ -231,6 +233,7 @@ class TestSongSelectImport(TestCase, TestMixin):
|
||||
MockedBeautifulSoup.return_value = mocked_results_page
|
||||
mock_callback = MagicMock()
|
||||
importer = SongSelectImport(None)
|
||||
importer.subscription_level = 'premium'
|
||||
|
||||
# WHEN: The search method is called
|
||||
results = importer.search('text', 1000, mock_callback)
|
||||
@ -282,6 +285,7 @@ class TestSongSelectImport(TestCase, TestMixin):
|
||||
MockedBeautifulSoup.return_value = mocked_results_page
|
||||
mock_callback = MagicMock()
|
||||
importer = SongSelectImport(None)
|
||||
importer.subscription_level = 'premium'
|
||||
|
||||
# WHEN: The search method is called
|
||||
results = importer.search('text', 2, mock_callback)
|
||||
|
@ -45,8 +45,8 @@ class TestSongUsage(TestCase):
|
||||
# THEN: about() should return a string object
|
||||
assert isinstance(SongUsagePlugin.about(), str)
|
||||
# THEN: about() should return a non-empty string
|
||||
assert len(SongUsagePlugin.about()) is not 0
|
||||
assert len(SongUsagePlugin.about()) is not 0
|
||||
assert len(SongUsagePlugin.about()) != 0
|
||||
assert len(SongUsagePlugin.about()) != 0
|
||||
|
||||
@patch('openlp.plugins.songusage.songusageplugin.Manager')
|
||||
def test_song_usage_init(self, MockedManager):
|
||||
|
@ -134,6 +134,7 @@ class TestProjectorDB(TestCase, TestMixin):
|
||||
"""
|
||||
Set up anything necessary for all tests
|
||||
"""
|
||||
self.tmp_folder = mkdtemp(prefix='openlp_')
|
||||
# Create a test app to keep from segfaulting
|
||||
Registry.create()
|
||||
self.registry = Registry()
|
||||
@ -153,11 +154,11 @@ class TestProjectorDB(TestCase, TestMixin):
|
||||
patch('openlp.core.ui.mainwindow.ThemeManager'), \
|
||||
patch('openlp.core.ui.mainwindow.ProjectorManager'), \
|
||||
patch('openlp.core.ui.mainwindow.websockets.WebSocketServer'), \
|
||||
patch('openlp.core.ui.mainwindow.server.HttpServer'):
|
||||
patch('openlp.core.ui.mainwindow.server.HttpServer'), \
|
||||
patch('openlp.core.state.State.list_plugins') as mock_plugins:
|
||||
mock_plugins.return_value = []
|
||||
self.main_window = MainWindow()
|
||||
|
||||
# Create a temporary database directory and database
|
||||
self.tmp_folder = mkdtemp(prefix='openlp_')
|
||||
tmpdb_url = 'sqlite:///{db}'.format(db=os.path.join(self.tmp_folder, TEST_DB))
|
||||
mocked_init_url.return_value = tmpdb_url
|
||||
self.projector = ProjectorDB()
|
||||
|
@ -452,12 +452,16 @@ class TestPJLinkCommands(TestCase):
|
||||
"""
|
||||
Test saving video source available information
|
||||
"""
|
||||
|
||||
# GIVEN: Test object
|
||||
with patch.object(openlp.core.projectors.pjlink, 'log') as mock_log:
|
||||
pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True)
|
||||
pjlink.source_available = []
|
||||
log_debug_calls = [call('({ip}) Setting projector sources_available to '
|
||||
log_debug_calls = [call('({ip}) reset_information() connect status is '
|
||||
'S_NOT_CONNECTED'.format(ip=pjlink.name)),
|
||||
call('({ip}) Setting projector source_available to '
|
||||
'"[\'11\', \'12\', \'21\', \'22\', \'31\', \'32\']"'.format(ip=pjlink.name))]
|
||||
|
||||
chk_data = '21 12 11 22 32 31' # Although they should already be sorted, use unsorted to verify method
|
||||
chk_test = ['11', '12', '21', '22', '31', '32']
|
||||
|
||||
|
@ -24,7 +24,7 @@ Package to test for proper bzr tags.
|
||||
"""
|
||||
import os
|
||||
from subprocess import PIPE, Popen
|
||||
from unittest import TestCase
|
||||
from unittest import TestCase, SkipTest
|
||||
|
||||
|
||||
TAGS1 = {'1.9.0', '1.9.1', '1.9.2', '1.9.3', '1.9.4', '1.9.5', '1.9.6', '1.9.7', '1.9.8', '1.9.9', '1.9.10',
|
||||
@ -42,7 +42,10 @@ class TestBzrTags(TestCase):
|
||||
path = os.path.dirname(__file__)
|
||||
|
||||
# WHEN getting the branches tags
|
||||
bzr = Popen(('bzr', 'tags', '--directory=' + path), stdout=PIPE)
|
||||
try:
|
||||
bzr = Popen(('bzr', 'tags', '--directory=' + path), stdout=PIPE)
|
||||
except Exception:
|
||||
raise SkipTest('bzr is not installed')
|
||||
std_out = bzr.communicate()[0]
|
||||
count = len(TAGS1)
|
||||
tags = [line.decode('utf-8').split()[0] for line in std_out.splitlines()]
|
||||
|
Loading…
Reference in New Issue
Block a user