diff --git a/nose2.cfg b/nose2.cfg
index 1f2e01126..ae73407d7 100644
--- a/nose2.cfg
+++ b/nose2.cfg
@@ -1,5 +1,5 @@
[unittest]
-verbose = true
+verbose = True
plugins = nose2.plugins.mp
[log-capture]
@@ -9,14 +9,19 @@ filter = -nose
log-level = ERROR
[test-result]
-always-on = true
-descriptions = true
+always-on = True
+descriptions = True
[coverage]
-always-on = true
+always-on = True
coverage = openlp
coverage-report = html
[multiprocess]
-always-on = false
+always-on = False
processes = 4
+
+[output-buffer]
+always-on = True
+stderr = True
+stdout = False
diff --git a/openlp/core/api/deploy.py b/openlp/core/api/deploy.py
index 0419b45db..b64cc40d5 100644
--- a/openlp/core/api/deploy.py
+++ b/openlp/core/api/deploy.py
@@ -22,7 +22,6 @@
"""
Download and "install" the remote web client
"""
-import os
from zipfile import ZipFile
from openlp.core.common.applocation import AppLocation
@@ -30,18 +29,18 @@ from openlp.core.common.registry import Registry
from openlp.core.common.httputils import url_get_file, get_web_page, get_url_file_size
-def deploy_zipfile(app_root, zip_name):
+def deploy_zipfile(app_root_path, zip_name):
"""
Process the downloaded zip file and add to the correct directory
- :param zip_name: the zip file to be processed
- :param app_root: the directory where the zip get expanded to
+ :param str zip_name: the zip file name to be processed
+ :param openlp.core.common.path.Path app_root_path: The directory to expand the zip to
:return: None
"""
- zip_file = os.path.join(app_root, zip_name)
- web_zip = ZipFile(zip_file)
- web_zip.extractall(app_root)
+ zip_path = app_root_path / zip_name
+ web_zip = ZipFile(str(zip_path))
+ web_zip.extractall(str(app_root_path))
def download_sha256():
@@ -67,4 +66,4 @@ def download_and_check(callback=None):
if url_get_file(callback, 'https://get.openlp.org/webclient/site.zip',
AppLocation.get_section_data_path('remotes') / 'site.zip',
sha256=sha256):
- deploy_zipfile(str(AppLocation.get_section_data_path('remotes')), 'site.zip')
+ deploy_zipfile(AppLocation.get_section_data_path('remotes'), 'site.zip')
diff --git a/openlp/core/api/endpoint/controller.py b/openlp/core/api/endpoint/controller.py
index 8ecfdb732..13e8d1681 100644
--- a/openlp/core/api/endpoint/controller.py
+++ b/openlp/core/api/endpoint/controller.py
@@ -28,6 +28,7 @@ import json
from openlp.core.api.http.endpoint import Endpoint
from openlp.core.api.http import requires_auth
from openlp.core.common.applocation import AppLocation
+from openlp.core.common.path import Path
from openlp.core.common.registry import Registry
from openlp.core.common.settings import Settings
from openlp.core.lib import ItemCapabilities, create_thumb
@@ -66,12 +67,12 @@ def controller_text(request):
elif current_item.is_image() and not frame.get('image', '') and Settings().value('api/thumbnails'):
item['tag'] = str(index + 1)
thumbnail_path = os.path.join('images', 'thumbnails', frame['title'])
- full_thumbnail_path = str(AppLocation.get_data_path() / thumbnail_path)
+ full_thumbnail_path = AppLocation.get_data_path() / thumbnail_path
# Create thumbnail if it doesn't exists
- if not os.path.exists(full_thumbnail_path):
- create_thumb(current_item.get_frame_path(index), full_thumbnail_path, False)
- Registry().get('image_manager').add_image(full_thumbnail_path, frame['title'], None, 88, 88)
- item['img'] = urllib.request.pathname2url(os.path.sep + thumbnail_path)
+ if not full_thumbnail_path.exists():
+ create_thumb(Path(current_item.get_frame_path(index)), full_thumbnail_path, False)
+ Registry().get('image_manager').add_image(str(full_thumbnail_path), frame['title'], None, 88, 88)
+ item['img'] = urllib.request.pathname2url(os.path.sep + str(thumbnail_path))
item['text'] = str(frame['title'])
item['html'] = str(frame['title'])
else:
diff --git a/openlp/core/api/endpoint/core.py b/openlp/core/api/endpoint/core.py
index 2988e03aa..8b9bcc4c0 100644
--- a/openlp/core/api/endpoint/core.py
+++ b/openlp/core/api/endpoint/core.py
@@ -172,15 +172,3 @@ def main_image(request):
'slide_image': 'data:image/png;base64,' + str(image_to_byte(live_controller.slide_image))
}
return {'results': result}
-
-
-def get_content_type(file_name):
- """
- Examines the extension of the file and determines what the content_type should be, defaults to text/plain
- Returns the extension and the content_type
-
- :param file_name: name of file
- """
- ext = os.path.splitext(file_name)[1]
- content_type = FILE_TYPES.get(ext, 'text/plain')
- return ext, content_type
diff --git a/openlp/core/api/endpoint/pluginhelpers.py b/openlp/core/api/endpoint/pluginhelpers.py
index 9377bde6a..b8f606fbc 100644
--- a/openlp/core/api/endpoint/pluginhelpers.py
+++ b/openlp/core/api/endpoint/pluginhelpers.py
@@ -19,7 +19,6 @@
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
-import os
import json
import re
import urllib
@@ -103,7 +102,7 @@ def display_thumbnails(request, controller_name, log, dimensions, file_name, sli
:param controller_name: which controller is requesting the image
:param log: the logger object
:param dimensions: the image size eg 88x88
- :param file_name: the file name of the image
+ :param str file_name: the file name of the image
:param slide: the individual image name
:return:
"""
@@ -124,12 +123,10 @@ def display_thumbnails(request, controller_name, log, dimensions, file_name, sli
if controller_name and file_name:
file_name = urllib.parse.unquote(file_name)
if '..' not in file_name: # no hacking please
+ full_path = AppLocation.get_section_data_path(controller_name) / 'thumbnails' / file_name
if slide:
- full_path = str(AppLocation.get_section_data_path(controller_name) / 'thumbnails' / file_name / slide)
- else:
- full_path = str(AppLocation.get_section_data_path(controller_name) / 'thumbnails' / file_name)
- if os.path.exists(full_path):
- path, just_file_name = os.path.split(full_path)
- Registry().get('image_manager').add_image(full_path, just_file_name, None, width, height)
- image = Registry().get('image_manager').get_image(full_path, just_file_name, width, height)
+ full_path = full_path / slide
+ if full_path.exists():
+ Registry().get('image_manager').add_image(full_path, full_path.name, None, width, height)
+ image = Registry().get('image_manager').get_image(full_path, full_path.name, width, height)
return Response(body=image_to_byte(image, False), status=200, content_type='image/png', charset='utf8')
diff --git a/openlp/core/api/endpoint/remote.py b/openlp/core/api/endpoint/remote.py
index 4741ada15..3a408f74e 100644
--- a/openlp/core/api/endpoint/remote.py
+++ b/openlp/core/api/endpoint/remote.py
@@ -27,7 +27,7 @@ from openlp.core.api.endpoint.core import TRANSLATED_STRINGS
log = logging.getLogger(__name__)
-remote_endpoint = Endpoint('remote', template_dir='remotes', static_dir='remotes')
+remote_endpoint = Endpoint('remote', template_dir='remotes')
@remote_endpoint.route('{view}')
diff --git a/openlp/core/api/http/endpoint.py b/openlp/core/api/http/endpoint.py
index fe2b11d9a..011b7e73a 100644
--- a/openlp/core/api/http/endpoint.py
+++ b/openlp/core/api/http/endpoint.py
@@ -22,8 +22,6 @@
"""
The Endpoint class, which provides plugins with a way to serve their own portion of the API
"""
-import os
-
from mako.template import Template
from openlp.core.common.applocation import AppLocation
@@ -67,13 +65,17 @@ class Endpoint(object):
def render_template(self, filename, **kwargs):
"""
Render a mako template
+
+ :param str filename: The file name of the template to render.
+ :return: The rendered template object.
+ :rtype: mako.template.Template
"""
- root = str(AppLocation.get_section_data_path('remotes'))
+ root_path = AppLocation.get_section_data_path('remotes')
if not self.template_dir:
raise Exception('No template directory specified')
- path = os.path.join(root, self.template_dir, filename)
+ path = root_path / self.template_dir / filename
if self.static_dir:
kwargs['static_url'] = '/{prefix}/static'.format(prefix=self.url_prefix)
kwargs['static_url'] = kwargs['static_url'].replace('//', '/')
kwargs['assets_url'] = '/assets'
- return Template(filename=path, input_encoding='utf-8').render(**kwargs)
+ return Template(filename=str(path), input_encoding='utf-8').render(**kwargs)
diff --git a/openlp/core/api/http/wsgiapp.py b/openlp/core/api/http/wsgiapp.py
index f948d4096..4c863b7cb 100644
--- a/openlp/core/api/http/wsgiapp.py
+++ b/openlp/core/api/http/wsgiapp.py
@@ -25,7 +25,6 @@ App stuff
"""
import json
import logging
-import os
import re
from webob import Request, Response
@@ -138,12 +137,11 @@ class WSGIApplication(object):
Add a static directory as a route
"""
if route not in self.static_routes:
- root = str(AppLocation.get_section_data_path('remotes'))
- static_path = os.path.abspath(os.path.join(root, static_dir))
- if not os.path.exists(static_path):
+ static_path = AppLocation.get_section_data_path('remotes') / static_dir
+ if not static_path.exists():
log.error('Static path "%s" does not exist. Skipping creating static route/', static_path)
return
- self.static_routes[route] = DirectoryApp(static_path)
+ self.static_routes[route] = DirectoryApp(str(static_path.resolve()))
def dispatch(self, request):
"""
diff --git a/openlp/core/common/__init__.py b/openlp/core/common/__init__.py
index f8017fdbd..99e222041 100644
--- a/openlp/core/common/__init__.py
+++ b/openlp/core/common/__init__.py
@@ -43,9 +43,13 @@ log = logging.getLogger(__name__ + '.__init__')
FIRST_CAMEL_REGEX = re.compile('(.)([A-Z][a-z]+)')
SECOND_CAMEL_REGEX = re.compile('([a-z0-9])([A-Z])')
-CONTROL_CHARS = re.compile(r'[\x00-\x1F\x7F-\x9F]', re.UNICODE)
-INVALID_FILE_CHARS = re.compile(r'[\\/:\*\?"<>\|\+\[\]%]', re.UNICODE)
+CONTROL_CHARS = re.compile(r'[\x00-\x1F\x7F-\x9F]')
+INVALID_FILE_CHARS = re.compile(r'[\\/:\*\?"<>\|\+\[\]%]')
IMAGES_FILTER = None
+REPLACMENT_CHARS_MAP = str.maketrans({'\u2018': '\'', '\u2019': '\'', '\u201c': '"', '\u201d': '"', '\u2026': '...',
+ '\u2013': '-', '\u2014': '-', '\v': '\n\n', '\f': '\n\n'})
+NEW_LINE_REGEX = re.compile(r' ?(\r\n?|\n) ?')
+WHITESPACE_REGEX = re.compile(r'[ \t]+')
def trace_error_handler(logger):
@@ -314,17 +318,6 @@ def get_filesystem_encoding():
return encoding
-def split_filename(path):
- """
- Return a list of the parts in a given path.
- """
- path = os.path.abspath(path)
- if not os.path.isfile(path):
- return path, ''
- else:
- return os.path.split(path)
-
-
def delete_file(file_path):
"""
Deletes a file from the system.
@@ -339,7 +332,7 @@ def delete_file(file_path):
if file_path.exists():
file_path.unlink()
return True
- except (IOError, OSError):
+ except OSError:
log.exception('Unable to delete file {file_path}'.format(file_path=file_path))
return False
@@ -436,3 +429,17 @@ def get_file_encoding(file_path):
return detector.result
except OSError:
log.exception('Error detecting file encoding')
+
+
+def normalize_str(irreg_str):
+ """
+ Normalize the supplied string. Remove unicode control chars and tidy up white space.
+
+ :param str irreg_str: The string to normalize.
+ :return: The normalized string
+ :rtype: str
+ """
+ irreg_str = irreg_str.translate(REPLACMENT_CHARS_MAP)
+ irreg_str = CONTROL_CHARS.sub('', irreg_str)
+ irreg_str = NEW_LINE_REGEX.sub('\n', irreg_str)
+ return WHITESPACE_REGEX.sub(' ', irreg_str)
diff --git a/openlp/core/common/applocation.py b/openlp/core/common/applocation.py
index cf70edfc1..279c49002 100644
--- a/openlp/core/common/applocation.py
+++ b/openlp/core/common/applocation.py
@@ -83,7 +83,7 @@ class AppLocation(object):
"""
# Check if we have a different data location.
if Settings().contains('advanced/data path'):
- path = Settings().value('advanced/data path')
+ path = Path(Settings().value('advanced/data path'))
else:
path = AppLocation.get_directory(AppLocation.DataDir)
create_paths(path)
diff --git a/openlp/core/common/httputils.py b/openlp/core/common/httputils.py
index 11ae7b563..21b778b80 100644
--- a/openlp/core/common/httputils.py
+++ b/openlp/core/common/httputils.py
@@ -97,8 +97,8 @@ def get_web_page(url, headers=None, update_openlp=False, proxies=None):
response = requests.get(url, headers=headers, proxies=proxies, timeout=float(CONNECTION_TIMEOUT))
log.debug('Downloaded page {url}'.format(url=response.url))
break
- except IOError:
- # For now, catch IOError. All requests errors inherit from IOError
+ except OSError:
+ # For now, catch OSError. All requests errors inherit from OSError
log.exception('Unable to connect to {url}'.format(url=url))
response = None
if retries >= CONNECTION_RETRIES:
@@ -127,7 +127,7 @@ def get_url_file_size(url):
try:
response = requests.head(url, timeout=float(CONNECTION_TIMEOUT), allow_redirects=True)
return int(response.headers['Content-Length'])
- except IOError:
+ except OSError:
if retries > CONNECTION_RETRIES:
raise ConnectionError('Unable to download {url}'.format(url=url))
else:
@@ -173,7 +173,7 @@ def url_get_file(callback, url, file_path, sha256=None):
file_path.unlink()
return False
break
- except IOError:
+ except OSError:
trace_error_handler(log)
if retries > CONNECTION_RETRIES:
if file_path.exists():
diff --git a/openlp/core/common/i18n.py b/openlp/core/common/i18n.py
index 1f4357808..9149f3fe6 100644
--- a/openlp/core/common/i18n.py
+++ b/openlp/core/common/i18n.py
@@ -53,7 +53,7 @@ def translate(context, text, comment=None, qt_translate=QtCore.QCoreApplication.
Language = namedtuple('Language', ['id', 'name', 'code'])
ICU_COLLATOR = None
-DIGITS_OR_NONDIGITS = re.compile(r'\d+|\D+', re.UNICODE)
+DIGITS_OR_NONDIGITS = re.compile(r'\d+|\D+')
LANGUAGES = sorted([
Language(1, translate('common.languages', '(Afan) Oromo', 'Language code: om'), 'om'),
Language(2, translate('common.languages', 'Abkhazian', 'Language code: ab'), 'ab'),
diff --git a/openlp/core/common/mixins.py b/openlp/core/common/mixins.py
index 1bc6907a0..a07940e10 100644
--- a/openlp/core/common/mixins.py
+++ b/openlp/core/common/mixins.py
@@ -101,6 +101,20 @@ class RegistryProperties(object):
"""
This adds registry components to classes to use at run time.
"""
+ _application = None
+ _plugin_manager = None
+ _image_manager = None
+ _media_controller = None
+ _service_manager = None
+ _preview_controller = None
+ _live_controller = None
+ _main_window = None
+ _renderer = None
+ _theme_manager = None
+ _settings_form = None
+ _alerts_manager = None
+ _projector_manager = None
+
@property
def application(self):
"""
diff --git a/openlp/core/common/path.py b/openlp/core/common/path.py
index 19e17470b..0e4b45c2a 100644
--- a/openlp/core/common/path.py
+++ b/openlp/core/common/path.py
@@ -69,6 +69,16 @@ class Path(PathVariant):
path = path.relative_to(base_path)
return {'__Path__': path.parts}
+ def rmtree(self, ignore_errors=False, onerror=None):
+ """
+ Provide an interface to :func:`shutil.rmtree`
+
+ :param bool ignore_errors: Ignore errors
+ :param onerror: Handler function to handle any errors
+ :rtype: None
+ """
+ shutil.rmtree(str(self), ignore_errors, onerror)
+
def replace_params(args, kwargs, params):
"""
@@ -153,23 +163,6 @@ def copytree(*args, **kwargs):
return str_to_path(shutil.copytree(*args, **kwargs))
-def rmtree(*args, **kwargs):
- """
- Wraps :func:shutil.rmtree` so that we can accept Path objects.
-
- :param openlp.core.common.path.Path path: Takes a Path object which is then converted to a str object
- :return: Passes the return from :func:`shutil.rmtree` back
- :rtype: None
-
- See the following link for more information on the other parameters:
- https://docs.python.org/3/library/shutil.html#shutil.rmtree
- """
-
- args, kwargs = replace_params(args, kwargs, ((0, 'path', path_to_str),))
-
- return shutil.rmtree(*args, **kwargs)
-
-
def which(*args, **kwargs):
"""
Wraps :func:shutil.which` so that it return a Path objects.
@@ -233,7 +226,7 @@ def create_paths(*paths, **kwargs):
try:
if not path.exists():
path.mkdir(parents=True)
- except IOError:
+ except OSError:
if not kwargs.get('do_not_log', False):
log.exception('failed to check if directory exists or create directory')
diff --git a/openlp/core/common/registry.py b/openlp/core/common/registry.py
index 252274d4d..4c9e92108 100644
--- a/openlp/core/common/registry.py
+++ b/openlp/core/common/registry.py
@@ -151,8 +151,9 @@ class Registry(object):
trace_error_handler(log)
log.exception('Exception for function {function}'.format(function=function))
else:
- trace_error_handler(log)
- log.exception('Event {event} called but not registered'.format(event=event))
+ if log.getEffectiveLevel() == logging.DEBUG:
+ trace_error_handler(log)
+ log.exception('Event {event} called but not registered'.format(event=event))
return results
def get_flag(self, key):
diff --git a/openlp/core/common/settings.py b/openlp/core/common/settings.py
index a8b448c74..3e9f91595 100644
--- a/openlp/core/common/settings.py
+++ b/openlp/core/common/settings.py
@@ -40,7 +40,7 @@ __version__ = 2
# Fix for bug #1014422.
X11_BYPASS_DEFAULT = True
-if is_linux():
+if is_linux(): # pragma: no cover
# Default to False on Gnome.
X11_BYPASS_DEFAULT = bool(not os.environ.get('GNOME_DESKTOP_SESSION_ID'))
# Default to False on Xfce.
@@ -230,11 +230,14 @@ class Settings(QtCore.QSettings):
'projector/source dialog type': 0 # Source select dialog box type
}
__file_path__ = ''
+ # Settings upgrades prior to 3.0
__setting_upgrade_1__ = [
- # Changed during 2.2.x development.
('songs/search as type', 'advanced/search as type', []),
('media/players', 'media/players_temp', [(media_players_conv, None)]), # Convert phonon to system
('media/players_temp', 'media/players', []), # Move temp setting from above to correct setting
+ ]
+ # Settings upgrades for 3.0 (aka 2.6)
+ __setting_upgrade_2__ = [
('advanced/default color', 'core/logo background color', []), # Default image renamed + moved to general > 2.4.
('advanced/default image', 'core/logo file', []), # Default image renamed + moved to general after 2.4.
('remotes/https enabled', '', []),
@@ -255,9 +258,7 @@ class Settings(QtCore.QSettings):
# Last search type was renamed to last used search type in 2.6 since Bible search value type changed in 2.6.
('songs/last search type', 'songs/last used search type', []),
('bibles/last search type', '', []),
- ('custom/last search type', 'custom/last used search type', [])]
-
- __setting_upgrade_2__ = [
+ ('custom/last search type', 'custom/last used search type', []),
# The following changes are being made for the conversion to using Path objects made in 2.6 development
('advanced/data path', 'advanced/data path', [(str_to_path, None)]),
('crashreport/last directory', 'crashreport/last directory', [(str_to_path, None)]),
@@ -280,6 +281,9 @@ class Settings(QtCore.QSettings):
('presentations/last directory', 'presentations/last directory', [(str_to_path, None)]),
('images/last directory', 'images/last directory', [(str_to_path, None)]),
('media/last directory', 'media/last directory', [(str_to_path, None)]),
+ ('songuasge/db password', 'songusage/db password', []),
+ ('songuasge/db hostname', 'songusage/db hostname', []),
+ ('songuasge/db database', 'songusage/db database', []),
(['core/monitor', 'core/x position', 'core/y position', 'core/height', 'core/width', 'core/override',
'core/display on monitor'], 'core/monitors', [(upgrade_monitor, [1, 0, 0, None, None, False, False])])
]
@@ -490,32 +494,38 @@ class Settings(QtCore.QSettings):
for version in range(current_version, __version__):
version += 1
upgrade_list = getattr(self, '__setting_upgrade_{version}__'.format(version=version))
- for old_key, new_key, rules in upgrade_list:
+ for old_keys, new_key, rules in upgrade_list:
# Once removed we don't have to do this again. - Can be removed once fully switched to the versioning
# system.
- if not self.contains(old_key):
+ if not isinstance(old_keys, (tuple, list)):
+ old_keys = [old_keys]
+ if any([not self.contains(old_key) for old_key in old_keys]):
+ log.warning('One of {} does not exist, skipping upgrade'.format(old_keys))
continue
if new_key:
# Get the value of the old_key.
- old_value = super(Settings, self).value(old_key)
+ old_values = [super(Settings, self).value(old_key) for old_key in old_keys]
# When we want to convert the value, we have to figure out the default value (because we cannot get
# the default value from the central settings dict.
if rules:
- default_value = rules[0][1]
- old_value = self._convert_value(old_value, default_value)
+ default_values = rules[0][1]
+ if not isinstance(default_values, (list, tuple)):
+ default_values = [default_values]
+ old_values = [self._convert_value(old_value, default_value)
+ for old_value, default_value in zip(old_values, default_values)]
# Iterate over our rules and check what the old_value should be "converted" to.
- for new, old in rules:
+ new_value = None
+ for new_rule, old_rule in rules:
# If the value matches with the condition (rule), then use the provided value. This is used to
# convert values. E. g. an old value 1 results in True, and 0 in False.
- if callable(new):
- old_value = new(old_value)
- elif old == old_value:
- old_value = new
+ if callable(new_rule):
+ new_value = new_rule(*old_values)
+ elif old_rule in old_values:
+ new_value = new_rule
break
- self.setValue(new_key, old_value)
- if new_key != old_key:
- self.remove(old_key)
- self.setValue('settings/version', version)
+ self.setValue(new_key, new_value)
+ [self.remove(old_key) for old_key in old_keys if old_key != new_key]
+ self.setValue('settings/version', version)
def value(self, key):
"""
diff --git a/openlp/core/lib/__init__.py b/openlp/core/lib/__init__.py
index c09025529..e4d9c4943 100644
--- a/openlp/core/lib/__init__.py
+++ b/openlp/core/lib/__init__.py
@@ -99,7 +99,7 @@ def get_text_file_string(text_file_path):
# no BOM was found
file_handle.seek(0)
content = file_handle.read()
- except (IOError, UnicodeError):
+ except (OSError, UnicodeError):
log.exception('Failed to open text file {text}'.format(text=text_file_path))
return content
@@ -174,8 +174,9 @@ def create_thumb(image_path, thumb_path, return_icon=True, size=None):
height of 88 is used.
:return: The final icon.
"""
- ext = os.path.splitext(thumb_path)[1].lower()
- reader = QtGui.QImageReader(image_path)
+ # TODO: To path object
+ thumb_path = Path(thumb_path)
+ reader = QtGui.QImageReader(str(image_path))
if size is None:
# No size given; use default height of 88
if reader.size().isEmpty():
@@ -202,10 +203,10 @@ def create_thumb(image_path, thumb_path, return_icon=True, size=None):
# Invalid; use default height of 88
reader.setScaledSize(QtCore.QSize(int(ratio * 88), 88))
thumb = reader.read()
- thumb.save(thumb_path, ext[1:])
+ thumb.save(str(thumb_path), thumb_path.suffix[1:].lower())
if not return_icon:
return
- if os.path.exists(thumb_path):
+ if thumb_path.exists():
return build_icon(thumb_path)
# Fallback for files with animation support.
return build_icon(image_path)
@@ -312,6 +313,3 @@ from .serviceitem import ServiceItem, ServiceItemType, ItemCapabilities
from .htmlbuilder import build_html, build_lyrics_format_css, build_lyrics_outline_css, build_chords_css
from .imagemanager import ImageManager
from .mediamanageritem import MediaManagerItem
-from .projector.db import ProjectorDB, Projector
-from .projector.pjlink import PJLink
-from .projector.constants import PJLINK_PORT, ERROR_MSG, ERROR_STRING
diff --git a/openlp/core/lib/mediamanageritem.py b/openlp/core/lib/mediamanageritem.py
index 425fa0523..55306267c 100644
--- a/openlp/core/lib/mediamanageritem.py
+++ b/openlp/core/lib/mediamanageritem.py
@@ -92,7 +92,7 @@ class MediaManagerItem(QtWidgets.QWidget, RegistryProperties):
Run some initial setup. This method is separate from __init__ in order to mock it out in tests.
"""
self.hide()
- self.whitespace = re.compile(r'[\W_]+', re.UNICODE)
+ self.whitespace = re.compile(r'[\W_]+')
visible_title = self.plugin.get_string(StringContent.VisibleName)
self.title = str(visible_title['title'])
Registry().register(self.plugin.name, self)
@@ -318,10 +318,10 @@ class MediaManagerItem(QtWidgets.QWidget, RegistryProperties):
self, self.on_new_prompt,
Settings().value(self.settings_section + '/last directory'),
self.on_new_file_masks)
- log.info('New files(s) {file_paths}'.format(file_paths=file_paths))
+ log.info('New file(s) {file_paths}'.format(file_paths=file_paths))
if file_paths:
self.application.set_busy_cursor()
- self.validate_and_load([path_to_str(path) for path in file_paths])
+ self.validate_and_load(file_paths)
self.application.set_normal_cursor()
def load_file(self, data):
@@ -330,21 +330,24 @@ class MediaManagerItem(QtWidgets.QWidget, RegistryProperties):
:param data: A dictionary containing the list of files to be loaded and the target
"""
- new_files = []
+ new_file_paths = []
error_shown = False
for file_name in data['files']:
- file_type = file_name.split('.')[-1]
- if file_type.lower() not in self.on_new_file_masks:
+ file_path = str_to_path(file_name)
+ if file_path.suffix[1:].lower() not in self.on_new_file_masks:
if not error_shown:
- critical_error_message_box(translate('OpenLP.MediaManagerItem', 'Invalid File Type'),
- translate('OpenLP.MediaManagerItem',
- 'Invalid File {name}.\n'
- 'Suffix not supported').format(name=file_name))
+ critical_error_message_box(
+ translate('OpenLP.MediaManagerItem', 'Invalid File Type'),
+ translate('OpenLP.MediaManagerItem',
+ 'Invalid File {file_path}.\nFile extension not supported').format(
+ file_path=file_path))
error_shown = True
else:
- new_files.append(file_name)
- if new_files:
- self.validate_and_load(new_files, data['target'])
+ new_file_paths.append(file_path)
+ if new_file_paths:
+ if 'target' in data:
+ self.validate_and_load(new_file_paths, data['target'])
+ self.validate_and_load(new_file_paths)
def dnd_move_internal(self, target):
"""
@@ -354,12 +357,12 @@ class MediaManagerItem(QtWidgets.QWidget, RegistryProperties):
"""
pass
- def validate_and_load(self, files, target_group=None):
+ def validate_and_load(self, file_paths, target_group=None):
"""
Process a list for files either from the File Dialog or from Drag and
Drop
- :param files: The files to be loaded.
+ :param list[openlp.core.common.path.Path] file_paths: The files to be loaded.
:param target_group: The QTreeWidgetItem of the group that will be the parent of the added files
"""
full_list = []
@@ -367,18 +370,17 @@ class MediaManagerItem(QtWidgets.QWidget, RegistryProperties):
full_list.append(self.list_view.item(count).data(QtCore.Qt.UserRole))
duplicates_found = False
files_added = False
- for file_path in files:
- if file_path in full_list:
+ for file_path in file_paths:
+ if path_to_str(file_path) in full_list:
duplicates_found = True
else:
files_added = True
- full_list.append(file_path)
+ full_list.append(path_to_str(file_path))
if full_list and files_added:
if target_group is None:
self.list_view.clear()
self.load_list(full_list, target_group)
- last_dir = os.path.split(files[0])[0]
- Settings().setValue(self.settings_section + '/last directory', Path(last_dir))
+ Settings().setValue(self.settings_section + '/last directory', file_paths[0].parent)
Settings().setValue('{section}/{section} files'.format(section=self.settings_section), self.get_file_list())
if duplicates_found:
critical_error_message_box(UiStrings().Duplicate,
diff --git a/openlp/core/lib/plugin.py b/openlp/core/lib/plugin.py
index f155b3ce7..7e3b2e416 100644
--- a/openlp/core/lib/plugin.py
+++ b/openlp/core/lib/plugin.py
@@ -139,10 +139,6 @@ class Plugin(QtCore.QObject, RegistryProperties):
self.text_strings = {}
self.set_plugin_text_strings()
self.name_strings = self.text_strings[StringContent.Name]
- if version:
- self.version = version
- else:
- self.version = get_version()['version']
self.settings_section = self.name
self.icon = None
self.media_item_class = media_item_class
@@ -162,6 +158,19 @@ class Plugin(QtCore.QObject, RegistryProperties):
Settings.extend_default_settings(default_settings)
Registry().register_function('{name}_add_service_item'.format(name=self.name), self.process_add_service_event)
Registry().register_function('{name}_config_updated'.format(name=self.name), self.config_update)
+ self._setup(version)
+
+ def _setup(self, version):
+ """
+ Run some initial setup. This method is separate from __init__ in order to mock it out in tests.
+
+ :param version: Defaults to *None*, which means that the same version number is used as OpenLP's version number.
+ :rtype: None
+ """
+ if version:
+ self.version = version
+ else:
+ self.version = get_version()['version']
def check_pre_conditions(self):
"""
diff --git a/openlp/core/lib/pluginmanager.py b/openlp/core/lib/pluginmanager.py
index 061788b25..d4a02f8c0 100644
--- a/openlp/core/lib/pluginmanager.py
+++ b/openlp/core/lib/pluginmanager.py
@@ -43,8 +43,7 @@ class PluginManager(RegistryBase, LogMixin, RegistryProperties):
"""
super(PluginManager, self).__init__(parent)
self.log_info('Plugin manager Initialising')
- self.base_path = os.path.abspath(str(AppLocation.get_directory(AppLocation.PluginsDir)))
- self.log_debug('Base path {path}'.format(path=self.base_path))
+ self.log_debug('Base path {path}'.format(path=AppLocation.get_directory(AppLocation.PluginsDir)))
self.plugins = []
self.log_info('Plugin manager Initialised')
diff --git a/openlp/core/lib/projector/__init__.py b/openlp/core/projectors/__init__.py
similarity index 90%
rename from openlp/core/lib/projector/__init__.py
rename to openlp/core/projectors/__init__.py
index dc7d2c89d..396422902 100644
--- a/openlp/core/lib/projector/__init__.py
+++ b/openlp/core/projectors/__init__.py
@@ -20,11 +20,13 @@
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
- :mod:`openlp.core.ui.projector`
+ :mod:`openlp.core.projectors`
- Initialization for the openlp.core.ui.projector modules.
+ Initialization for the openlp.core.projectors modules.
"""
+from openlp.core.projectors.constants import PJLINK_PORT, ERROR_MSG, ERROR_STRING
+
class DialogSourceStyle(object):
"""
diff --git a/openlp/core/lib/projector/constants.py b/openlp/core/projectors/constants.py
similarity index 100%
rename from openlp/core/lib/projector/constants.py
rename to openlp/core/projectors/constants.py
diff --git a/openlp/core/lib/projector/db.py b/openlp/core/projectors/db.py
similarity index 99%
rename from openlp/core/lib/projector/db.py
rename to openlp/core/projectors/db.py
index fa8934ae2..99fe9515b 100644
--- a/openlp/core/lib/projector/db.py
+++ b/openlp/core/projectors/db.py
@@ -43,8 +43,8 @@ from sqlalchemy.ext.declarative import declarative_base, declared_attr
from sqlalchemy.orm import relationship
from openlp.core.lib.db import Manager, init_db, init_url
-from openlp.core.lib.projector.constants import PJLINK_DEFAULT_CODES
-from openlp.core.lib.projector import upgrade
+from openlp.core.projectors.constants import PJLINK_DEFAULT_CODES
+from openlp.core.projectors import upgrade
Base = declarative_base(MetaData())
diff --git a/openlp/core/ui/projector/editform.py b/openlp/core/projectors/editform.py
similarity index 99%
rename from openlp/core/ui/projector/editform.py
rename to openlp/core/projectors/editform.py
index bd3267665..4ae1f96d9 100644
--- a/openlp/core/ui/projector/editform.py
+++ b/openlp/core/projectors/editform.py
@@ -30,8 +30,8 @@ from PyQt5 import QtCore, QtWidgets
from openlp.core.common import verify_ip_address
from openlp.core.common.i18n import translate
from openlp.core.lib import build_icon
-from openlp.core.lib.projector.db import Projector
-from openlp.core.lib.projector.constants import PJLINK_PORT
+from openlp.core.projectors.db import Projector
+from openlp.core.projectors.constants import PJLINK_PORT
log = logging.getLogger(__name__)
log.debug('editform loaded')
diff --git a/openlp/core/ui/projector/manager.py b/openlp/core/projectors/manager.py
similarity index 98%
rename from openlp/core/ui/projector/manager.py
rename to openlp/core/projectors/manager.py
index 0770886e4..b352858b1 100644
--- a/openlp/core/ui/projector/manager.py
+++ b/openlp/core/projectors/manager.py
@@ -34,14 +34,14 @@ from openlp.core.common.mixins import LogMixin, RegistryProperties
from openlp.core.common.registry import RegistryBase
from openlp.core.common.settings import Settings
from openlp.core.lib.ui import create_widget_action
-from openlp.core.lib.projector import DialogSourceStyle
-from openlp.core.lib.projector.constants import ERROR_MSG, ERROR_STRING, E_AUTHENTICATION, E_ERROR, \
+from openlp.core.projectors import DialogSourceStyle
+from openlp.core.projectors.constants import ERROR_MSG, ERROR_STRING, E_AUTHENTICATION, E_ERROR, \
E_NETWORK, E_NOT_CONNECTED, E_UNKNOWN_SOCKET_ERROR, STATUS_STRING, S_CONNECTED, S_CONNECTING, S_COOLDOWN, \
S_INITIALIZE, S_NOT_CONNECTED, S_OFF, S_ON, S_STANDBY, S_WARMUP
-from openlp.core.lib.projector.db import ProjectorDB
-from openlp.core.lib.projector.pjlink import PJLink, PJLinkUDP
-from openlp.core.ui.projector.editform import ProjectorEditForm
-from openlp.core.ui.projector.sourceselectform import SourceSelectTabs, SourceSelectSingle
+from openlp.core.projectors.db import ProjectorDB
+from openlp.core.projectors.pjlink import PJLink, PJLinkUDP
+from openlp.core.projectors.editform import ProjectorEditForm
+from openlp.core.projectors.sourceselectform import SourceSelectTabs, SourceSelectSingle
from openlp.core.widgets.toolbar import OpenLPToolbar
log = logging.getLogger(__name__)
@@ -518,7 +518,7 @@ class ProjectorManager(QtWidgets.QWidget, RegistryBase, UiProjectorManager, LogM
projector.thread.quit()
new_list = []
for item in self.projector_list:
- if item.link.dbid == projector.link.dbid:
+ if item.link.db_item.id == projector.link.db_item.id:
continue
new_list.append(item)
self.projector_list = new_list
@@ -672,14 +672,16 @@ class ProjectorManager(QtWidgets.QWidget, RegistryBase, UiProjectorManager, LogM
data=projector.model_filter)
count = 1
for item in projector.link.lamp:
+ if item['On'] is None:
+ status = translate('OpenLP.ProjectorManager', 'Unavailable')
+ elif item['On']:
+ status = translate('OpenLP.ProjectorManager', 'ON')
+ else:
+ status = translate('OpenLP.ProjectorManager', 'OFF')
message += '{title} {count} {status} '.format(title=translate('OpenLP.ProjectorManager',
'Lamp'),
count=count,
- status=translate('OpenLP.ProjectorManager',
- 'ON')
- if item['On']
- else translate('OpenLP.ProjectorManager',
- 'OFF'))
+ status=status)
message += '{title}: {hours}
'.format(title=translate('OpenLP.ProjectorManager', 'Hours'),
hours=item['Hours'])
@@ -730,7 +732,6 @@ class ProjectorManager(QtWidgets.QWidget, RegistryBase, UiProjectorManager, LogM
thread.started.connect(item.link.thread_started)
thread.finished.connect(item.link.thread_stopped)
thread.finished.connect(thread.deleteLater)
- item.link.projectorNetwork.connect(self.update_status)
item.link.changeStatus.connect(self.update_status)
item.link.projectorAuthentication.connect(self.authentication_error)
item.link.projectorNoAuthentication.connect(self.no_authentication_error)
diff --git a/openlp/core/lib/projector/pjlink.py b/openlp/core/projectors/pjlink.py
similarity index 98%
rename from openlp/core/lib/projector/pjlink.py
rename to openlp/core/projectors/pjlink.py
index 2272b971f..16a65bd11 100644
--- a/openlp/core/lib/projector/pjlink.py
+++ b/openlp/core/projectors/pjlink.py
@@ -54,7 +54,7 @@ from PyQt5 import QtCore, QtNetwork
from openlp.core.common import qmd5_hash
from openlp.core.common.i18n import translate
-from openlp.core.lib.projector.constants import CONNECTION_ERRORS, CR, ERROR_MSG, ERROR_STRING, \
+from openlp.core.projectors.constants import CONNECTION_ERRORS, CR, ERROR_MSG, ERROR_STRING, \
E_AUTHENTICATION, E_CONNECTION_REFUSED, E_GENERAL, E_INVALID_DATA, E_NETWORK, E_NOT_CONNECTED, E_OK, \
E_PARAMETER, E_PROJECTOR, E_SOCKET_TIMEOUT, E_UNAVAILABLE, E_UNDEFINED, PJLINK_ERRORS, PJLINK_ERST_DATA, \
PJLINK_ERST_STATUS, PJLINK_MAX_PACKET, PJLINK_PORT, PJLINK_POWR_STATUS, PJLINK_VALID_CMD, \
@@ -402,17 +402,20 @@ class PJLinkCommands(object):
:param data: Lamp(s) status.
"""
lamps = []
- data_dict = data.split()
- while data_dict:
- try:
- fill = {'Hours': int(data_dict[0]), 'On': False if data_dict[1] == '0' else True}
- except ValueError:
- # In case of invalid entry
- log.warning('({ip}) process_lamp(): Invalid data "{data}"'.format(ip=self.ip, data=data))
- return
- lamps.append(fill)
- data_dict.pop(0) # Remove lamp hours
- data_dict.pop(0) # Remove lamp on/off
+ lamp_list = data.split()
+ if len(lamp_list) < 2:
+ lamps.append({'Hours': int(lamp_list[0]), 'On': None})
+ else:
+ while lamp_list:
+ try:
+ fill = {'Hours': int(lamp_list[0]), 'On': False if lamp_list[1] == '0' else True}
+ except ValueError:
+ # In case of invalid entry
+ log.warning('({ip}) process_lamp(): Invalid data "{data}"'.format(ip=self.ip, data=data))
+ return
+ lamps.append(fill)
+ lamp_list.pop(0) # Remove lamp hours
+ lamp_list.pop(0) # Remove lamp on/off
self.lamp = lamps
return
@@ -520,7 +523,6 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands):
"""
# Signals sent by this module
changeStatus = QtCore.pyqtSignal(str, int, str)
- projectorNetwork = QtCore.pyqtSignal(int) # Projector network activity
projectorStatus = QtCore.pyqtSignal(int) # Status update
projectorAuthentication = QtCore.pyqtSignal(str) # Authentication error
projectorNoAuthentication = QtCore.pyqtSignal(str) # PIN set and no authentication needed
@@ -846,7 +848,6 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands):
log.debug('({ip}) get_socket(): No data available (-1)'.format(ip=self.ip))
return self.receive_data_signal()
self.socket_timer.stop()
- self.projectorNetwork.emit(S_NETWORK_RECEIVED)
return self.get_data(buff=read, ip=self.ip)
def get_data(self, buff, ip):
@@ -925,7 +926,6 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands):
if cmd not in PJLINK_VALID_CMD:
log.error('({ip}) send_command(): Invalid command requested - ignoring.'.format(ip=self.ip))
return
- self.projectorNetwork.emit(S_NETWORK_SENDING)
log.debug('({ip}) send_command(): Building cmd="{command}" opts="{data}"{salt}'.format(ip=self.ip,
command=cmd,
data=opts,
@@ -996,7 +996,6 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands):
log.debug('({ip}) _send_string(): Sending "{data}"'.format(ip=self.ip, data=out.strip()))
log.debug('({ip}) _send_string(): Queue = {data}'.format(ip=self.ip, data=self.send_queue))
self.socket_timer.start()
- self.projectorNetwork.emit(S_NETWORK_SENDING)
sent = self.write(out.encode('{string_encoding}'.format(string_encoding='utf-8' if utf8 else 'ascii')))
self.waitForBytesWritten(2000) # 2 seconds should be enough
if sent == -1:
diff --git a/openlp/core/ui/projector/sourceselectform.py b/openlp/core/projectors/sourceselectform.py
similarity index 99%
rename from openlp/core/ui/projector/sourceselectform.py
rename to openlp/core/projectors/sourceselectform.py
index 0c150d25b..aaf8170ac 100644
--- a/openlp/core/ui/projector/sourceselectform.py
+++ b/openlp/core/projectors/sourceselectform.py
@@ -31,8 +31,8 @@ from PyQt5 import QtCore, QtWidgets
from openlp.core.common import is_macosx
from openlp.core.common.i18n import translate
from openlp.core.lib import build_icon
-from openlp.core.lib.projector.db import ProjectorSource
-from openlp.core.lib.projector.constants import PJLINK_DEFAULT_SOURCES, PJLINK_DEFAULT_CODES
+from openlp.core.projectors.db import ProjectorSource
+from openlp.core.projectors.constants import PJLINK_DEFAULT_SOURCES, PJLINK_DEFAULT_CODES
log = logging.getLogger(__name__)
diff --git a/openlp/core/ui/projector/tab.py b/openlp/core/projectors/tab.py
similarity index 99%
rename from openlp/core/ui/projector/tab.py
rename to openlp/core/projectors/tab.py
index b7c2e5dda..29e6a9511 100644
--- a/openlp/core/ui/projector/tab.py
+++ b/openlp/core/projectors/tab.py
@@ -29,7 +29,7 @@ from PyQt5 import QtWidgets
from openlp.core.common.i18n import UiStrings, translate
from openlp.core.common.settings import Settings
from openlp.core.lib import SettingsTab
-from openlp.core.lib.projector import DialogSourceStyle
+from openlp.core.projectors import DialogSourceStyle
log = logging.getLogger(__name__)
log.debug('projectortab module loaded')
diff --git a/openlp/core/lib/projector/upgrade.py b/openlp/core/projectors/upgrade.py
similarity index 100%
rename from openlp/core/lib/projector/upgrade.py
rename to openlp/core/projectors/upgrade.py
diff --git a/openlp/core/ui/__init__.py b/openlp/core/ui/__init__.py
index feac3ee45..91b610e4f 100644
--- a/openlp/core/ui/__init__.py
+++ b/openlp/core/ui/__init__.py
@@ -113,9 +113,10 @@ from .formattingtagcontroller import FormattingTagController
from .shortcutlistform import ShortcutListForm
from .servicemanager import ServiceManager
from .thememanager import ThemeManager
-from .projector.manager import ProjectorManager
-from .projector.tab import ProjectorTab
-from .projector.editform import ProjectorEditForm
+
+from openlp.core.projectors.editform import ProjectorEditForm
+from openlp.core.projectors.manager import ProjectorManager
+from openlp.core.projectors.tab import ProjectorTab
__all__ = ['SplashScreen', 'AboutForm', 'SettingsForm', 'MainDisplay', 'SlideController', 'ServiceManager', 'ThemeForm',
'ThemeManager', 'ServiceItemEditForm', 'FirstTimeForm', 'FirstTimeLanguageForm', 'Display', 'AudioPlayer',
diff --git a/openlp/core/ui/exceptionform.py b/openlp/core/ui/exceptionform.py
index d2d4e5140..82f132e2b 100644
--- a/openlp/core/ui/exceptionform.py
+++ b/openlp/core/ui/exceptionform.py
@@ -108,7 +108,7 @@ class ExceptionForm(QtWidgets.QDialog, Ui_ExceptionDialog, RegistryProperties):
try:
with file_path.open('w') as report_file:
report_file.write(report_text)
- except IOError:
+ except OSError:
log.exception('Failed to write crash report')
def on_send_report_button_clicked(self):
diff --git a/openlp/core/ui/firsttimewizard.py b/openlp/core/ui/firsttimewizard.py
index be893cd5e..5212479ef 100644
--- a/openlp/core/ui/firsttimewizard.py
+++ b/openlp/core/ui/firsttimewizard.py
@@ -261,8 +261,8 @@ class UiFirstTimeWizard(object):
self.alert_check_box.setText(translate('OpenLP.FirstTimeWizard',
'Alerts – Display informative messages while showing other slides'))
self.projectors_check_box.setText(translate('OpenLP.FirstTimeWizard',
- 'Projectors – Control PJLink compatible projects on your network'
- ' from OpenLP'))
+ '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.'))
diff --git a/openlp/core/ui/formattingtagcontroller.py b/openlp/core/ui/formattingtagcontroller.py
index e92173fed..4b9d75fee 100644
--- a/openlp/core/ui/formattingtagcontroller.py
+++ b/openlp/core/ui/formattingtagcontroller.py
@@ -43,7 +43,7 @@ class FormattingTagController(object):
r'(?P[^\s/!\?>]+)(?:\s+[^\s=]+="[^"]*")*\s*(?P/)?'
r'|(?P!\[CDATA\[(?:(?!\]\]>).)*\]\])'
r'|(?P\?(?:(?!\?>).)*\?)'
- r'|(?P!--(?:(?!-->).)*--))>', re.UNICODE)
+ r'|(?P!--(?:(?!-->).)*--))>')
self.html_regex = re.compile(r'^(?:[^<>]*%s)*[^<>]*$' % self.html_tag_regex.pattern)
def pre_save(self):
diff --git a/openlp/core/ui/mainwindow.py b/openlp/core/ui/mainwindow.py
index ea714c08a..4e8213e56 100644
--- a/openlp/core/ui/mainwindow.py
+++ b/openlp/core/ui/mainwindow.py
@@ -40,24 +40,24 @@ from openlp.core.common import is_win, is_macosx, add_actions
from openlp.core.common.actions import ActionList, CategoryOrder
from openlp.core.common.applocation import AppLocation
from openlp.core.common.i18n import LanguageManager, UiStrings, translate
-from openlp.core.common.path import Path, copyfile, create_paths, path_to_str, str_to_path
+from openlp.core.common.path import Path, copyfile, create_paths
from openlp.core.common.mixins import RegistryProperties
from openlp.core.common.registry import Registry
from openlp.core.common.settings import Settings
from openlp.core.display.screens import ScreenList
from openlp.core.lib import PluginManager, ImageManager, PluginStatus, build_icon
from openlp.core.lib.ui import create_action
+from openlp.core.projectors.manager import ProjectorManager
from openlp.core.ui import AboutForm, SettingsForm, ServiceManager, ThemeManager, PluginForm, ShortcutListForm, \
FormattingTagForm
from openlp.core.ui.slidecontroller import LiveController, PreviewController
from openlp.core.ui.firsttimeform import FirstTimeForm
-from openlp.core.widgets.dialogs import FileDialog
-from openlp.core.widgets.docks import OpenLPDockWidget, MediaDockManager
from openlp.core.ui.media import MediaController
from openlp.core.ui.printserviceform import PrintServiceForm
-from openlp.core.ui.projector.manager import ProjectorManager
from openlp.core.ui.style import PROGRESSBAR_STYLE, get_library_stylesheet
from openlp.core.version import get_version
+from openlp.core.widgets.dialogs import FileDialog
+from openlp.core.widgets.docks import OpenLPDockWidget, MediaDockManager
log = logging.getLogger(__name__)
@@ -180,7 +180,7 @@ class Ui_MainWindow(object):
triggers=self.service_manager_contents.on_load_service_clicked)
self.file_save_item = create_action(main_window, 'fileSaveItem', icon=':/general/general_save.png',
can_shortcuts=True, category=UiStrings().File,
- triggers=self.service_manager_contents.save_file)
+ triggers=self.service_manager_contents.decide_save_method)
self.file_save_as_item = create_action(main_window, 'fileSaveAsItem', can_shortcuts=True,
category=UiStrings().File,
triggers=self.service_manager_contents.save_file_as)
@@ -296,10 +296,9 @@ class Ui_MainWindow(object):
# Give QT Extra Hint that this is an About Menu Item
self.about_item.setMenuRole(QtWidgets.QAction.AboutRole)
if is_win():
- self.local_help_file = os.path.join(str(AppLocation.get_directory(AppLocation.AppDir)), 'OpenLP.chm')
+ self.local_help_file = AppLocation.get_directory(AppLocation.AppDir) / 'OpenLP.chm'
elif is_macosx():
- self.local_help_file = os.path.join(str(AppLocation.get_directory(AppLocation.AppDir)),
- '..', 'Resources', 'OpenLP.help')
+ self.local_help_file = AppLocation.get_directory(AppLocation.AppDir) / '..' / 'Resources' / 'OpenLP.help'
self.user_manual_item = create_action(main_window, 'userManualItem', icon=':/system/system_help_contents.png',
can_shortcuts=True, category=UiStrings().Help,
triggers=self.on_help_clicked)
@@ -375,7 +374,7 @@ class Ui_MainWindow(object):
self.media_manager_dock.setWindowTitle(translate('OpenLP.MainWindow', 'Library'))
self.service_manager_dock.setWindowTitle(translate('OpenLP.MainWindow', 'Service'))
self.theme_manager_dock.setWindowTitle(translate('OpenLP.MainWindow', 'Themes'))
- self.projector_manager_dock.setWindowTitle(translate('OpenLP.MainWindow', 'Projectors'))
+ self.projector_manager_dock.setWindowTitle(translate('OpenLP.MainWindow', 'Projector Controller'))
self.file_new_item.setText(translate('OpenLP.MainWindow', '&New Service'))
self.file_new_item.setToolTip(UiStrings().NewService)
self.file_new_item.setStatusTip(UiStrings().CreateService)
@@ -407,7 +406,7 @@ class Ui_MainWindow(object):
translate('OpenLP.MainWindow', 'Import settings from a *.config file previously exported from '
'this or another machine.'))
self.settings_import_item.setText(translate('OpenLP.MainWindow', 'Settings'))
- self.view_projector_manager_item.setText(translate('OpenLP.MainWindow', '&Projectors'))
+ self.view_projector_manager_item.setText(translate('OpenLP.MainWindow', '&Projector Controller'))
self.view_projector_manager_item.setToolTip(translate('OpenLP.MainWindow', 'Hide or show Projectors.'))
self.view_projector_manager_item.setStatusTip(translate('OpenLP.MainWindow',
'Toggle visibility of the Projectors.'))
@@ -649,8 +648,8 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
self.application.process_events()
plugin.first_time()
self.application.process_events()
- temp_dir = os.path.join(str(gettempdir()), 'openlp')
- shutil.rmtree(temp_dir, True)
+ temp_path = Path(gettempdir(), 'openlp')
+ temp_path.rmtree(True)
def on_first_time_wizard_clicked(self):
"""
@@ -759,7 +758,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
Use the Online manual in other cases. (Linux)
"""
if is_macosx() or is_win():
- QtGui.QDesktopServices.openUrl(QtCore.QUrl.fromLocalFile(self.local_help_file))
+ QtGui.QDesktopServices.openUrl(QtCore.QUrl.fromLocalFile(str(self.local_help_file)))
else:
import webbrowser
webbrowser.open_new('http://manual.openlp.org/')
@@ -1219,7 +1218,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
settings.remove('custom slide')
settings.remove('service')
settings.beginGroup(self.general_settings_section)
- self.recent_files = [path_to_str(file_path) for file_path in settings.value('recent files')]
+ self.recent_files = settings.value('recent files')
settings.endGroup()
settings.beginGroup(self.ui_settings_section)
self.move(settings.value('main window position'))
@@ -1243,7 +1242,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
log.debug('Saving QSettings')
settings = Settings()
settings.beginGroup(self.general_settings_section)
- settings.setValue('recent files', [str_to_path(file) for file in self.recent_files])
+ settings.setValue('recent files', self.recent_files)
settings.endGroup()
settings.beginGroup(self.ui_settings_section)
settings.setValue('main window position', self.pos())
@@ -1259,26 +1258,24 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
Updates the recent file menu with the latest list of service files accessed.
"""
recent_file_count = Settings().value('advanced/recent file count')
- existing_recent_files = [recentFile for recentFile in self.recent_files if os.path.isfile(str(recentFile))]
- recent_files_to_display = existing_recent_files[0:recent_file_count]
self.recent_files_menu.clear()
- for file_id, filename in enumerate(recent_files_to_display):
- log.debug('Recent file name: {name}'.format(name=filename))
+ count = 0
+ for recent_path in self.recent_files:
+ if not recent_path.is_file():
+ continue
+ count += 1
+ log.debug('Recent file name: {name}'.format(name=recent_path))
action = create_action(self, '',
- text='&{n} {name}'.format(n=file_id + 1,
- name=os.path.splitext(os.path.basename(str(filename)))[0]),
- data=filename,
- triggers=self.service_manager_contents.on_recent_service_clicked)
+ text='&{n} {name}'.format(n=count, name=recent_path.name),
+ data=recent_path, triggers=self.service_manager_contents.on_recent_service_clicked)
self.recent_files_menu.addAction(action)
- clear_recent_files_action = create_action(self, '',
- text=translate('OpenLP.MainWindow', 'Clear List', 'Clear List of '
- 'recent files'),
- statustip=translate('OpenLP.MainWindow', 'Clear the list of recent '
- 'files.'),
- enabled=bool(self.recent_files),
- triggers=self.clear_recent_file_menu)
+ if count == recent_file_count:
+ break
+ clear_recent_files_action = \
+ create_action(self, '', text=translate('OpenLP.MainWindow', 'Clear List', 'Clear List of recent files'),
+ statustip=translate('OpenLP.MainWindow', 'Clear the list of recent files.'),
+ enabled=bool(self.recent_files), triggers=self.clear_recent_file_menu)
add_actions(self.recent_files_menu, (None, clear_recent_files_action))
- clear_recent_files_action.setEnabled(bool(self.recent_files))
def add_recent_file(self, filename):
"""
@@ -1290,20 +1287,13 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
# actually stored in the settings therefore the default value of 20 will
# always be used.
max_recent_files = Settings().value('advanced/max recent files')
- if filename:
- # Add some cleanup to reduce duplication in the recent file list
- filename = os.path.abspath(filename)
- # abspath() only capitalises the drive letter if it wasn't provided
- # in the given filename which then causes duplication.
- if filename[1:3] == ':\\':
- filename = filename[0].upper() + filename[1:]
- if filename in self.recent_files:
- self.recent_files.remove(filename)
- if not isinstance(self.recent_files, list):
- self.recent_files = [self.recent_files]
- self.recent_files.insert(0, filename)
- while len(self.recent_files) > max_recent_files:
- self.recent_files.pop()
+ file_path = Path(filename)
+ # Some cleanup to reduce duplication in the recent file list
+ file_path = file_path.resolve()
+ if file_path in self.recent_files:
+ self.recent_files.remove(file_path)
+ self.recent_files.insert(0, file_path)
+ self.recent_files = self.recent_files[:max_recent_files]
def clear_recent_file_menu(self):
"""
@@ -1366,7 +1356,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
'- Please wait for copy to finish').format(path=self.new_data_path))
dir_util.copy_tree(str(old_data_path), str(self.new_data_path))
log.info('Copy successful')
- except (IOError, os.error, DistutilsFileError) as why:
+ except (OSError, DistutilsFileError) as why:
self.application.set_normal_cursor()
log.exception('Data copy failed {err}'.format(err=str(why)))
err_text = translate('OpenLP.MainWindow',
diff --git a/openlp/core/ui/media/vendor/mediainfoWrapper.py b/openlp/core/ui/media/vendor/mediainfoWrapper.py
index 6f270d46e..97d936d6b 100644
--- a/openlp/core/ui/media/vendor/mediainfoWrapper.py
+++ b/openlp/core/ui/media/vendor/mediainfoWrapper.py
@@ -25,10 +25,8 @@ information related to the rwquested media.
"""
import json
import os
-from subprocess import Popen
-from tempfile import mkstemp
+from subprocess import check_output
-import six
from bs4 import BeautifulSoup, NavigableString
ENV_DICT = os.environ
@@ -80,7 +78,7 @@ class Track(object):
def to_data(self):
data = {}
- for k, v in six.iteritems(self.__dict__):
+ for k, v in self.__dict__.items():
if k != 'xml_dom_fragment':
data[k] = v
return data
@@ -100,20 +98,10 @@ class MediaInfoWrapper(object):
@staticmethod
def parse(filename, environment=ENV_DICT):
- command = ["mediainfo", "-f", "--Output=XML", filename]
- fileno_out, fname_out = mkstemp(suffix=".xml", prefix="media-")
- fileno_err, fname_err = mkstemp(suffix=".err", prefix="media-")
- fp_out = os.fdopen(fileno_out, 'r+b')
- fp_err = os.fdopen(fileno_err, 'r+b')
- p = Popen(command, stdout=fp_out, stderr=fp_err, env=environment)
- p.wait()
- fp_out.seek(0)
-
- xml_dom = MediaInfoWrapper.parse_xml_data_into_dom(fp_out.read())
- fp_out.close()
- fp_err.close()
- os.unlink(fname_out)
- os.unlink(fname_err)
+ xml = check_output(['mediainfo', '-f', '--Output=XML', '--Inform=OLDXML', filename])
+ if not xml.startswith(b'[0-9]+){sep_v})?' \
'(?P[0-9]+)(?P{sep_r}(?:(?:(?P' \
'[0-9]+){sep_v})?(?P[0-9]+)|{sep_e})?)?'.format_map(REFERENCE_SEPARATORS)
- REFERENCE_MATCHES['range'] = re.compile(r'^\s*{range}\s*$'.format(range=range_regex), re.UNICODE)
- REFERENCE_MATCHES['range_separator'] = re.compile(REFERENCE_SEPARATORS['sep_l'], re.UNICODE)
+ REFERENCE_MATCHES['range'] = re.compile(r'^\s*{range}\s*$'.format(range=range_regex))
+ REFERENCE_MATCHES['range_separator'] = re.compile(REFERENCE_SEPARATORS['sep_l'])
# full reference match: ((,(?!$)|(?=$)))+
REFERENCE_MATCHES['full'] = \
re.compile(r'^\s*(?!\s)(?P[\d]*[.]?[^\d\.]+)\.*(?(?:{range_regex}(?:{sep_l}(?!\s*$)|(?=\s*$)))+)\s*$'.format(
- range_regex=range_regex, sep_l=REFERENCE_SEPARATORS['sep_l']), re.UNICODE)
+ range_regex=range_regex, sep_l=REFERENCE_SEPARATORS['sep_l']))
def get_reference_separator(separator_type):
diff --git a/openlp/plugins/bibles/lib/bibleimport.py b/openlp/plugins/bibles/lib/bibleimport.py
index 962fe1fd2..7efb46f5d 100644
--- a/openlp/plugins/bibles/lib/bibleimport.py
+++ b/openlp/plugins/bibles/lib/bibleimport.py
@@ -37,23 +37,23 @@ class BibleImport(BibleDB, LogMixin, RegistryProperties):
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
- self.filename = kwargs['filename'] if 'filename' in kwargs else None
+ self.file_path = kwargs.get('file_path')
self.wizard = None
self.stop_import_flag = False
Registry().register_function('openlp_stop_wizard', self.stop_import)
@staticmethod
- def is_compressed(file):
+ def is_compressed(file_path):
"""
Check if the supplied file is compressed
- :param file: A path to the file to check
+ :param file_path: A path to the file to check
"""
- if is_zipfile(file):
+ if is_zipfile(str(file_path)):
critical_error_message_box(
message=translate('BiblesPlugin.BibleImport',
'The file "{file}" you supplied is compressed. You must decompress it before import.'
- ).format(file=file))
+ ).format(file=file_path))
return True
return False
@@ -143,24 +143,24 @@ class BibleImport(BibleDB, LogMixin, RegistryProperties):
self.log_debug('No book name supplied. Falling back to guess_id')
book_ref_id = guess_id
if not book_ref_id:
- raise ValidationError(msg='Could not resolve book_ref_id in "{}"'.format(self.filename))
+ raise ValidationError(msg='Could not resolve book_ref_id in "{}"'.format(self.file_path))
book_details = BiblesResourcesDB.get_book_by_id(book_ref_id)
if book_details is None:
raise ValidationError(msg='book_ref_id: {book_ref} Could not be found in the BibleResourcesDB while '
- 'importing {file}'.format(book_ref=book_ref_id, file=self.filename))
+ 'importing {file}'.format(book_ref=book_ref_id, file=self.file_path))
return self.create_book(name, book_ref_id, book_details['testament_id'])
- def parse_xml(self, filename, use_objectify=False, elements=None, tags=None):
+ def parse_xml(self, file_path, use_objectify=False, elements=None, tags=None):
"""
Parse and clean the supplied file by removing any elements or tags we don't use.
- :param filename: The filename of the xml file to parse. Str
+ :param file_path: The filename of the xml file to parse. Str
:param use_objectify: Use the objectify parser rather than the etree parser. (Bool)
:param elements: A tuple of element names (Str) to remove along with their content.
:param tags: A tuple of element names (Str) to remove, preserving their content.
:return: The root element of the xml document
"""
try:
- with open(filename, 'rb') as import_file:
+ with file_path.open('rb') as import_file:
# NOTE: We don't need to do any of the normal encoding detection here, because lxml does it's own
# encoding detection, and the two mechanisms together interfere with each other.
if not use_objectify:
@@ -207,17 +207,17 @@ class BibleImport(BibleDB, LogMixin, RegistryProperties):
self.log_debug('Stopping import')
self.stop_import_flag = True
- def validate_xml_file(self, filename, tag):
+ def validate_xml_file(self, file_path, tag):
"""
Validate the supplied file
- :param filename: The supplied file
+ :param file_path: The supplied file
:param tag: The expected root tag type
:return: True if valid. ValidationError is raised otherwise.
"""
- if BibleImport.is_compressed(filename):
+ if BibleImport.is_compressed(file_path):
raise ValidationError(msg='Compressed file')
- bible = self.parse_xml(filename, use_objectify=True)
+ bible = self.parse_xml(file_path, use_objectify=True)
if bible is None:
raise ValidationError(msg='Error when opening file')
root_tag = bible.tag.lower()
diff --git a/openlp/plugins/bibles/lib/biblestab.py b/openlp/plugins/bibles/lib/biblestab.py
index 21970c106..c5e67840a 100644
--- a/openlp/plugins/bibles/lib/biblestab.py
+++ b/openlp/plugins/bibles/lib/biblestab.py
@@ -41,11 +41,11 @@ class BiblesTab(SettingsTab):
"""
log.info('Bible Tab loaded')
- def _init_(self, parent, title, visible_title, icon_path):
+ def _init_(self, *args, **kwargs):
self.paragraph_style = True
self.show_new_chapters = False
self.display_style = 0
- super(BiblesTab, self).__init__(parent, title, visible_title, icon_path)
+ super().__init__(*args, **kwargs)
def setupUi(self):
self.setObjectName('BiblesTab')
diff --git a/openlp/plugins/bibles/lib/db.py b/openlp/plugins/bibles/lib/db.py
index bc8ce4150..13aeadfae 100644
--- a/openlp/plugins/bibles/lib/db.py
+++ b/openlp/plugins/bibles/lib/db.py
@@ -35,6 +35,7 @@ from sqlalchemy.orm.exc import UnmappedClassError
from openlp.core.common import clean_filename
from openlp.core.common.applocation import AppLocation
from openlp.core.common.i18n import translate
+from openlp.core.common.path import Path
from openlp.core.lib.db import BaseModel, init_db, Manager
from openlp.core.lib.ui import critical_error_message_box
from openlp.plugins.bibles.lib import BibleStrings, LanguageSelection, upgrade
@@ -129,10 +130,15 @@ class BibleDB(Manager):
:param parent:
:param kwargs:
``path``
- The path to the bible database file.
+ The path to the bible database file. Type: openlp.core.common.path.Path
``name``
The name of the database. This is also used as the file name for SQLite databases.
+
+ ``file``
+ Type: openlp.core.common.path.Path
+
+ :rtype: None
"""
log.info('BibleDB loaded')
self._setup(parent, **kwargs)
@@ -145,20 +151,20 @@ class BibleDB(Manager):
self.session = None
if 'path' not in kwargs:
raise KeyError('Missing keyword argument "path".')
+ self.path = kwargs['path']
if 'name' not in kwargs and 'file' not in kwargs:
raise KeyError('Missing keyword argument "name" or "file".')
if 'name' in kwargs:
self.name = kwargs['name']
if not isinstance(self.name, str):
self.name = str(self.name, 'utf-8')
- self.file = clean_filename(self.name) + '.sqlite'
+ # TODO: To path object
+ file_path = Path(clean_filename(self.name) + '.sqlite')
if 'file' in kwargs:
- self.file = kwargs['file']
- Manager.__init__(self, 'bibles', init_schema, self.file, upgrade)
+ file_path = kwargs['file']
+ Manager.__init__(self, 'bibles', init_schema, file_path, upgrade)
if self.session and 'file' in kwargs:
self.get_name()
- if 'path' in kwargs:
- self.path = kwargs['path']
self._is_web_bible = None
def get_name(self):
@@ -307,8 +313,7 @@ class BibleDB(Manager):
book_escaped = book
for character in RESERVED_CHARACTERS:
book_escaped = book_escaped.replace(character, '\\' + character)
- regex_book = re.compile('\\s*{book}\\s*'.format(book='\\s*'.join(book_escaped.split())),
- re.UNICODE | re.IGNORECASE)
+ regex_book = re.compile('\\s*{book}\\s*'.format(book='\\s*'.join(book_escaped.split())), re.IGNORECASE)
if language_selection == LanguageSelection.Bible:
db_book = self.get_book(book)
if db_book:
@@ -471,9 +476,9 @@ class BiblesResourcesDB(QtCore.QObject, Manager):
Return the cursor object. Instantiate one if it doesn't exist yet.
"""
if BiblesResourcesDB.cursor is None:
- file_path = os.path.join(str(AppLocation.get_directory(AppLocation.PluginsDir)),
- 'bibles', 'resources', 'bibles_resources.sqlite')
- conn = sqlite3.connect(file_path)
+ file_path = \
+ AppLocation.get_directory(AppLocation.PluginsDir) / 'bibles' / 'resources' / 'bibles_resources.sqlite'
+ conn = sqlite3.connect(str(file_path))
BiblesResourcesDB.cursor = conn.cursor()
return BiblesResourcesDB.cursor
@@ -759,17 +764,13 @@ class AlternativeBookNamesDB(QtCore.QObject, Manager):
If necessary loads up the database and creates the tables if the database doesn't exist.
"""
if AlternativeBookNamesDB.cursor is None:
- file_path = os.path.join(
- str(AppLocation.get_directory(AppLocation.DataDir)), 'bibles', 'alternative_book_names.sqlite')
- if not os.path.exists(file_path):
+ file_path = AppLocation.get_directory(AppLocation.DataDir) / 'bibles' / 'alternative_book_names.sqlite'
+ AlternativeBookNamesDB.conn = sqlite3.connect(str(file_path))
+ if not file_path.exists():
# create new DB, create table alternative_book_names
- AlternativeBookNamesDB.conn = sqlite3.connect(file_path)
AlternativeBookNamesDB.conn.execute(
'CREATE TABLE alternative_book_names(id INTEGER NOT NULL, '
'book_reference_id INTEGER, language_id INTEGER, name VARCHAR(50), PRIMARY KEY (id))')
- else:
- # use existing DB
- AlternativeBookNamesDB.conn = sqlite3.connect(file_path)
AlternativeBookNamesDB.cursor = AlternativeBookNamesDB.conn.cursor()
return AlternativeBookNamesDB.cursor
diff --git a/openlp/plugins/bibles/lib/importers/__init__.py b/openlp/plugins/bibles/lib/importers/__init__.py
index ea62548f4..f83bbd595 100644
--- a/openlp/plugins/bibles/lib/importers/__init__.py
+++ b/openlp/plugins/bibles/lib/importers/__init__.py
@@ -19,3 +19,6 @@
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
+"""
+The :mod:`~openlp.plugins.bibles.lib.importers` module contains importers for the Bibles plugin.
+"""
diff --git a/openlp/plugins/bibles/lib/importers/csvbible.py b/openlp/plugins/bibles/lib/importers/csvbible.py
index e897c4803..46124fd5e 100644
--- a/openlp/plugins/bibles/lib/importers/csvbible.py
+++ b/openlp/plugins/bibles/lib/importers/csvbible.py
@@ -73,8 +73,8 @@ class CSVBible(BibleImport):
"""
super().__init__(*args, **kwargs)
self.log_info(self.__class__.__name__)
- self.books_file = kwargs['booksfile']
- self.verses_file = kwargs['versefile']
+ self.books_path = kwargs['books_path']
+ self.verses_path = kwargs['verse_path']
@staticmethod
def get_book_name(name, books):
@@ -92,21 +92,22 @@ class CSVBible(BibleImport):
return book_name
@staticmethod
- def parse_csv_file(filename, results_tuple):
+ def parse_csv_file(file_path, results_tuple):
"""
Parse the supplied CSV file.
- :param filename: The name of the file to parse. Str
- :param results_tuple: The namedtuple to use to store the results. namedtuple
- :return: An iterable yielding namedtuples of type results_tuple
+ :param openlp.core.common.path.Path file_path: The name of the file to parse.
+ :param namedtuple results_tuple: The namedtuple to use to store the results.
+ :return: An list of namedtuples of type results_tuple
+ :rtype: list[namedtuple]
"""
try:
- encoding = get_file_encoding(Path(filename))['encoding']
- with open(filename, 'r', encoding=encoding, newline='') as csv_file:
+ encoding = get_file_encoding(file_path)['encoding']
+ with file_path.open('r', encoding=encoding, newline='') as csv_file:
csv_reader = csv.reader(csv_file, delimiter=',', quotechar='"')
return [results_tuple(*line) for line in csv_reader]
except (OSError, csv.Error):
- raise ValidationError(msg='Parsing "{file}" failed'.format(file=filename))
+ raise ValidationError(msg='Parsing "{file}" failed'.format(file=file_path))
def process_books(self, books):
"""
@@ -159,12 +160,12 @@ class CSVBible(BibleImport):
self.language_id = self.get_language(bible_name)
if not self.language_id:
return False
- books = self.parse_csv_file(self.books_file, Book)
+ books = self.parse_csv_file(self.books_path, Book)
self.wizard.progress_bar.setValue(0)
self.wizard.progress_bar.setMinimum(0)
self.wizard.progress_bar.setMaximum(len(books))
book_list = self.process_books(books)
- verses = self.parse_csv_file(self.verses_file, Verse)
+ verses = self.parse_csv_file(self.verses_path, Verse)
self.wizard.progress_bar.setValue(0)
self.wizard.progress_bar.setMaximum(len(books) + 1)
self.process_verses(verses, book_list)
diff --git a/openlp/plugins/bibles/lib/importers/opensong.py b/openlp/plugins/bibles/lib/importers/opensong.py
index 2ac72503d..8400c9bf7 100644
--- a/openlp/plugins/bibles/lib/importers/opensong.py
+++ b/openlp/plugins/bibles/lib/importers/opensong.py
@@ -46,7 +46,8 @@ def parse_chapter_number(number, previous_number):
:param number: The raw data from the xml
:param previous_number: The previous chapter number
- :return: Number of current chapter. (Int)
+ :return: Number of current chapter.
+ :rtype: int
"""
if number:
return int(number.split()[-1])
@@ -132,13 +133,13 @@ class OpenSongBible(BibleImport):
:param bible_name: The name of the bible being imported
:return: True if import completed, False if import was unsuccessful
"""
- self.log_debug('Starting OpenSong import from "{name}"'.format(name=self.filename))
- self.validate_xml_file(self.filename, 'bible')
- bible = self.parse_xml(self.filename, use_objectify=True)
+ self.log_debug('Starting OpenSong import from "{name}"'.format(name=self.file_path))
+ self.validate_xml_file(self.file_path, 'bible')
+ bible = self.parse_xml(self.file_path, use_objectify=True)
if bible is None:
return False
# No language info in the opensong format, so ask the user
- self.language_id = self.get_language_id(bible_name=self.filename)
+ self.language_id = self.get_language_id(bible_name=str(self.file_path))
if not self.language_id:
return False
self.process_books(bible.b)
diff --git a/openlp/plugins/bibles/lib/importers/osis.py b/openlp/plugins/bibles/lib/importers/osis.py
index f10e084bc..cde57954a 100644
--- a/openlp/plugins/bibles/lib/importers/osis.py
+++ b/openlp/plugins/bibles/lib/importers/osis.py
@@ -159,14 +159,14 @@ class OSISBible(BibleImport):
"""
Loads a Bible from file.
"""
- self.log_debug('Starting OSIS import from "{name}"'.format(name=self.filename))
- self.validate_xml_file(self.filename, '{http://www.bibletechnologies.net/2003/osis/namespace}osis')
- bible = self.parse_xml(self.filename, elements=REMOVABLE_ELEMENTS, tags=REMOVABLE_TAGS)
+ self.log_debug('Starting OSIS import from "{name}"'.format(name=self.file_path))
+ self.validate_xml_file(self.file_path, '{http://www.bibletechnologies.net/2003/osis/namespace}osis')
+ bible = self.parse_xml(self.file_path, elements=REMOVABLE_ELEMENTS, tags=REMOVABLE_TAGS)
if bible is None:
return False
# Find bible language
language = bible.xpath("//ns:osisText/@xml:lang", namespaces=NS)
- self.language_id = self.get_language_id(language[0] if language else None, bible_name=self.filename)
+ self.language_id = self.get_language_id(language[0] if language else None, bible_name=str(self.file_path))
if not self.language_id:
return False
self.process_books(bible)
diff --git a/openlp/plugins/bibles/lib/importers/sword.py b/openlp/plugins/bibles/lib/importers/sword.py
index fd7b91a44..dcb392754 100644
--- a/openlp/plugins/bibles/lib/importers/sword.py
+++ b/openlp/plugins/bibles/lib/importers/sword.py
@@ -60,7 +60,7 @@ class SwordBible(BibleImport):
bible = pysword_modules.get_bible_from_module(self.sword_key)
language = pysword_module_json['lang']
language = language[language.find('.') + 1:]
- language_id = self.get_language_id(language, bible_name=self.filename)
+ language_id = self.get_language_id(language, bible_name=str(self.file_path))
if not language_id:
return False
books = bible.get_structure().get_books()
diff --git a/openlp/plugins/bibles/lib/importers/wordproject.py b/openlp/plugins/bibles/lib/importers/wordproject.py
index bf5c5fc75..90d23a2b5 100644
--- a/openlp/plugins/bibles/lib/importers/wordproject.py
+++ b/openlp/plugins/bibles/lib/importers/wordproject.py
@@ -19,15 +19,14 @@
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
-import os
-import re
import logging
-from codecs import open as copen
+import re
from tempfile import TemporaryDirectory
from zipfile import ZipFile
from bs4 import BeautifulSoup, Tag, NavigableString
+from openlp.core.common.path import Path
from openlp.plugins.bibles.lib.bibleimport import BibleImport
BOOK_NUMBER_PATTERN = re.compile(r'\[(\d+)\]')
@@ -51,9 +50,9 @@ class WordProjectBible(BibleImport):
Unzip the file to a temporary directory
"""
self.tmp = TemporaryDirectory()
- zip_file = ZipFile(os.path.abspath(self.filename))
- zip_file.extractall(self.tmp.name)
- self.base_dir = os.path.join(self.tmp.name, os.path.splitext(os.path.basename(self.filename))[0])
+ with ZipFile(str(self.file_path)) as zip_file:
+ zip_file.extractall(self.tmp.name)
+ self.base_path = Path(self.tmp.name, self.file_path.stem)
def process_books(self):
"""
@@ -62,8 +61,7 @@ class WordProjectBible(BibleImport):
:param bible_data: parsed xml
:return: None
"""
- with copen(os.path.join(self.base_dir, 'index.htm'), encoding='utf-8', errors='ignore') as index_file:
- page = index_file.read()
+ page = (self.base_path / 'index.htm').read_text(encoding='utf-8', errors='ignore')
soup = BeautifulSoup(page, 'lxml')
bible_books = soup.find('div', 'textOptions').find_all('li')
book_count = len(bible_books)
@@ -93,9 +91,7 @@ class WordProjectBible(BibleImport):
:return: None
"""
log.debug(book_link)
- book_file = os.path.join(self.base_dir, os.path.normpath(book_link))
- with copen(book_file, encoding='utf-8', errors='ignore') as f:
- page = f.read()
+ page = (self.base_path / book_link).read_text(encoding='utf-8', errors='ignore')
soup = BeautifulSoup(page, 'lxml')
header_div = soup.find('div', 'textHeader')
chapters_p = header_div.find('p')
@@ -114,9 +110,8 @@ class WordProjectBible(BibleImport):
"""
Get the verses for a particular book
"""
- chapter_file_name = os.path.join(self.base_dir, '{:02d}'.format(book_number), '{}.htm'.format(chapter_number))
- with copen(chapter_file_name, encoding='utf-8', errors='ignore') as chapter_file:
- page = chapter_file.read()
+ chapter_file_path = self.base_path / '{:02d}'.format(book_number) / '{}.htm'.format(chapter_number)
+ page = chapter_file_path.read_text(encoding='utf-8', errors='ignore')
soup = BeautifulSoup(page, 'lxml')
text_body = soup.find('div', 'textBody')
if text_body:
@@ -158,9 +153,9 @@ class WordProjectBible(BibleImport):
"""
Loads a Bible from file.
"""
- self.log_debug('Starting WordProject import from "{name}"'.format(name=self.filename))
+ self.log_debug('Starting WordProject import from "{name}"'.format(name=self.file_path))
self._unzip_file()
- self.language_id = self.get_language_id(None, bible_name=self.filename)
+ self.language_id = self.get_language_id(None, bible_name=str(self.file_path))
result = False
if self.language_id:
self.process_books()
diff --git a/openlp/plugins/bibles/lib/importers/zefania.py b/openlp/plugins/bibles/lib/importers/zefania.py
index a3849a7a0..2b5eb68b8 100644
--- a/openlp/plugins/bibles/lib/importers/zefania.py
+++ b/openlp/plugins/bibles/lib/importers/zefania.py
@@ -45,13 +45,13 @@ class ZefaniaBible(BibleImport):
"""
Loads a Bible from file.
"""
- log.debug('Starting Zefania import from "{name}"'.format(name=self.filename))
+ log.debug('Starting Zefania import from "{name}"'.format(name=self.file_path))
success = True
try:
- xmlbible = self.parse_xml(self.filename, elements=REMOVABLE_ELEMENTS, tags=REMOVABLE_TAGS)
+ xmlbible = self.parse_xml(self.file_path, elements=REMOVABLE_ELEMENTS, tags=REMOVABLE_TAGS)
# Find bible language
language = xmlbible.xpath("/XMLBIBLE/INFORMATION/language/text()")
- language_id = self.get_language_id(language[0] if language else None, bible_name=self.filename)
+ language_id = self.get_language_id(language[0] if language else None, bible_name=str(self.file_path))
if not language_id:
return False
no_of_books = int(xmlbible.xpath('count(//BIBLEBOOK)'))
@@ -69,7 +69,7 @@ class ZefaniaBible(BibleImport):
log.debug('Could not find a name, will use number, basically a guess.')
book_ref_id = int(bnumber)
if not book_ref_id:
- log.error('Importing books from "{name}" failed'.format(name=self.filename))
+ log.error('Importing books from "{name}" failed'.format(name=self.file_path))
return False
book_details = BiblesResourcesDB.get_book_by_id(book_ref_id)
db_book = self.create_book(book_details['name'], book_ref_id, book_details['testament_id'])
diff --git a/openlp/plugins/bibles/lib/manager.py b/openlp/plugins/bibles/lib/manager.py
index 7290ca862..37586b4be 100644
--- a/openlp/plugins/bibles/lib/manager.py
+++ b/openlp/plugins/bibles/lib/manager.py
@@ -19,7 +19,6 @@
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
-
import logging
from openlp.core.common import delete_file
@@ -115,7 +114,7 @@ class BibleManager(LogMixin, RegistryProperties):
self.settings_section = 'bibles'
self.web = 'Web'
self.db_cache = None
- self.path = str(AppLocation.get_section_data_path(self.settings_section))
+ self.path = AppLocation.get_section_data_path(self.settings_section)
self.proxy_name = Settings().value(self.settings_section + '/proxy name')
self.suffix = '.sqlite'
self.import_wizard = None
@@ -128,20 +127,20 @@ class BibleManager(LogMixin, RegistryProperties):
of HTTPBible is loaded instead of the BibleDB class.
"""
log.debug('Reload bibles')
- files = [str(file) for file in AppLocation.get_files(self.settings_section, self.suffix)]
- if 'alternative_book_names.sqlite' in files:
- files.remove('alternative_book_names.sqlite')
- log.debug('Bible Files {text}'.format(text=files))
+ file_paths = AppLocation.get_files(self.settings_section, self.suffix)
+ if Path('alternative_book_names.sqlite') in file_paths:
+ file_paths.remove(Path('alternative_book_names.sqlite'))
+ log.debug('Bible Files {text}'.format(text=file_paths))
self.db_cache = {}
- for filename in files:
- bible = BibleDB(self.parent, path=self.path, file=filename)
+ for file_path in file_paths:
+ bible = BibleDB(self.parent, path=self.path, file=file_path)
if not bible.session:
continue
name = bible.get_name()
# Remove corrupted files.
if name is None:
bible.session.close_all()
- delete_file(Path(self.path, filename))
+ delete_file(self.path / file_path)
continue
log.debug('Bible Name: "{name}"'.format(name=name))
self.db_cache[name] = bible
@@ -150,7 +149,7 @@ class BibleManager(LogMixin, RegistryProperties):
source = self.db_cache[name].get_object(BibleMeta, 'download_source')
download_name = self.db_cache[name].get_object(BibleMeta, 'download_name').value
meta_proxy = self.db_cache[name].get_object(BibleMeta, 'proxy_server')
- web_bible = HTTPBible(self.parent, path=self.path, file=filename, download_source=source.value,
+ web_bible = HTTPBible(self.parent, path=self.path, file=file_path, download_source=source.value,
download_name=download_name)
if meta_proxy:
web_bible.proxy_server = meta_proxy.value
diff --git a/openlp/plugins/custom/lib/db.py b/openlp/plugins/custom/lib/db.py
index dc1f74567..6581ee5ae 100644
--- a/openlp/plugins/custom/lib/db.py
+++ b/openlp/plugins/custom/lib/db.py
@@ -26,7 +26,7 @@ the Custom plugin
from sqlalchemy import Column, Table, types
from sqlalchemy.orm import mapper
-from openlp.core.common.i18n import get_locale_key
+from openlp.core.common.i18n import get_natural_key
from openlp.core.lib.db import BaseModel, init_db
@@ -36,10 +36,10 @@ class CustomSlide(BaseModel):
"""
# By default sort the customs by its title considering language specific characters.
def __lt__(self, other):
- return get_locale_key(self.title) < get_locale_key(other.title)
+ return get_natural_key(self.title) < get_natural_key(other.title)
def __eq__(self, other):
- return get_locale_key(self.title) == get_locale_key(other.title)
+ return get_natural_key(self.title) == get_natural_key(other.title)
def __hash__(self):
"""
diff --git a/openlp/plugins/images/lib/mediaitem.py b/openlp/plugins/images/lib/mediaitem.py
index b030d26fd..25a8d2353 100644
--- a/openlp/plugins/images/lib/mediaitem.py
+++ b/openlp/plugins/images/lib/mediaitem.py
@@ -26,7 +26,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets
from openlp.core.common import delete_file, get_images_filter
from openlp.core.common.applocation import AppLocation
-from openlp.core.common.i18n import UiStrings, translate, get_locale_key
+from openlp.core.common.i18n import UiStrings, translate, get_natural_key
from openlp.core.common.path import Path, create_paths
from openlp.core.common.registry import Registry
from openlp.core.common.settings import Settings
@@ -81,8 +81,12 @@ class ImageMediaItem(MediaManagerItem):
self.add_group_action.setToolTip(UiStrings().AddGroupDot)
self.replace_action.setText(UiStrings().ReplaceBG)
self.replace_action.setToolTip(UiStrings().ReplaceLiveBG)
+ self.replace_action_context.setText(UiStrings().ReplaceBG)
+ self.replace_action_context.setToolTip(UiStrings().ReplaceLiveBG)
self.reset_action.setText(UiStrings().ResetBG)
self.reset_action.setToolTip(UiStrings().ResetLiveBG)
+ self.reset_action_context.setText(UiStrings().ResetBG)
+ self.reset_action_context.setToolTip(UiStrings().ResetLiveBG)
def required_icons(self):
"""
@@ -184,6 +188,13 @@ class ImageMediaItem(MediaManagerItem):
self.list_view,
text=translate('ImagePlugin', 'Add new image(s)'),
icon=':/general/general_open.png', triggers=self.on_file_click)
+ create_widget_action(self.list_view, separator=True)
+ self.replace_action_context = create_widget_action(
+ self.list_view, text=UiStrings().ReplaceBG, icon=':/slides/slide_theme.png',
+ triggers=self.on_replace_click)
+ self.reset_action_context = create_widget_action(
+ self.list_view, text=UiStrings().ReplaceLiveBG, icon=':/system/system_close.png',
+ visible=False, triggers=self.on_reset_click)
def add_start_header_bar(self):
"""
@@ -271,7 +282,7 @@ class ImageMediaItem(MediaManagerItem):
:param parent_group_id: The ID of the group that will be added recursively.
"""
image_groups = self.manager.get_all_objects(ImageGroups, ImageGroups.parent_id == parent_group_id)
- image_groups.sort(key=lambda group_object: get_locale_key(group_object.group_name))
+ image_groups.sort(key=lambda group_object: get_natural_key(group_object.group_name))
folder_icon = build_icon(':/images/image_group.png')
for image_group in image_groups:
group = QtWidgets.QTreeWidgetItem()
@@ -298,7 +309,7 @@ class ImageMediaItem(MediaManagerItem):
combobox.clear()
combobox.top_level_group_added = False
image_groups = self.manager.get_all_objects(ImageGroups, ImageGroups.parent_id == parent_group_id)
- image_groups.sort(key=lambda group_object: get_locale_key(group_object.group_name))
+ image_groups.sort(key=lambda group_object: get_natural_key(group_object.group_name))
for image_group in image_groups:
combobox.addItem(prefix + image_group.group_name, image_group.id)
self.fill_groups_combobox(combobox, image_group.id, prefix + ' ')
@@ -355,7 +366,7 @@ class ImageMediaItem(MediaManagerItem):
self.expand_group(open_group.id)
# Sort the images by its filename considering language specific.
# characters.
- images.sort(key=lambda image_object: get_locale_key(image_object.file_path.name))
+ images.sort(key=lambda image_object: get_natural_key(image_object.file_path.name))
for image in images:
log.debug('Loading image: {name}'.format(name=image.file_path))
file_name = image.file_path.name
@@ -390,6 +401,7 @@ class ImageMediaItem(MediaManagerItem):
:param files: A List of strings containing the filenames of the files to be loaded
:param target_group: The QTreeWidgetItem of the group that will be the parent of the added files
"""
+ file_paths = [Path(file) for file in file_paths]
self.application.set_normal_cursor()
self.load_list(file_paths, target_group)
last_dir = file_paths[0].parent
@@ -532,9 +544,9 @@ class ImageMediaItem(MediaManagerItem):
group_items.append(item)
if isinstance(item.data(0, QtCore.Qt.UserRole), ImageFilenames):
image_items.append(item)
- group_items.sort(key=lambda item: get_locale_key(item.text(0)))
+ group_items.sort(key=lambda item: get_natural_key(item.text(0)))
target_group.addChildren(group_items)
- image_items.sort(key=lambda item: get_locale_key(item.text(0)))
+ image_items.sort(key=lambda item: get_natural_key(item.text(0)))
target_group.addChildren(image_items)
def generate_slide_data(self, service_item, item=None, xml_version=False, remote=False,
@@ -658,6 +670,7 @@ class ImageMediaItem(MediaManagerItem):
Called to reset the Live background with the image selected.
"""
self.reset_action.setVisible(False)
+ self.reset_action_context.setVisible(False)
self.live_controller.display.reset_image()
def live_theme_changed(self):
@@ -665,6 +678,7 @@ class ImageMediaItem(MediaManagerItem):
Triggered by the change of theme in the slide controller.
"""
self.reset_action.setVisible(False)
+ self.reset_action_context.setVisible(False)
def on_replace_click(self):
"""
@@ -682,6 +696,7 @@ class ImageMediaItem(MediaManagerItem):
if file_path.exists():
if self.live_controller.display.direct_image(str(file_path), background):
self.reset_action.setVisible(True)
+ self.reset_action_context.setVisible(True)
else:
critical_error_message_box(
UiStrings().LiveBGError,
diff --git a/openlp/plugins/media/lib/mediaitem.py b/openlp/plugins/media/lib/mediaitem.py
index ed787166b..e47ed70e0 100644
--- a/openlp/plugins/media/lib/mediaitem.py
+++ b/openlp/plugins/media/lib/mediaitem.py
@@ -26,7 +26,7 @@ import os
from PyQt5 import QtCore, QtWidgets
from openlp.core.common.applocation import AppLocation
-from openlp.core.common.i18n import UiStrings, translate, get_locale_key
+from openlp.core.common.i18n import UiStrings, translate, get_natural_key
from openlp.core.common.path import Path, path_to_str, create_paths
from openlp.core.common.mixins import RegistryProperties
from openlp.core.common.registry import Registry
@@ -176,7 +176,7 @@ class MediaMediaItem(MediaManagerItem, RegistryProperties):
def add_custom_context_actions(self):
create_widget_action(self.list_view, separator=True)
self.replace_action_context = create_widget_action(
- self.list_view, text=UiStrings().ReplaceBG, icon=':/slides/slide_blank.png',
+ self.list_view, text=UiStrings().ReplaceBG, icon=':/slides/slide_theme.png',
triggers=self.on_replace_click)
self.reset_action_context = create_widget_action(
self.list_view, text=UiStrings().ReplaceLiveBG, icon=':/system/system_close.png',
@@ -362,7 +362,7 @@ class MediaMediaItem(MediaManagerItem, RegistryProperties):
:param media: The media
:param target_group:
"""
- media.sort(key=lambda file_name: get_locale_key(os.path.split(str(file_name))[1]))
+ media.sort(key=lambda file_name: get_natural_key(os.path.split(str(file_name))[1]))
for track in media:
track_info = QtCore.QFileInfo(track)
item_name = None
@@ -404,7 +404,7 @@ class MediaMediaItem(MediaManagerItem, RegistryProperties):
:return: The media list
"""
media_file_paths = Settings().value(self.settings_section + '/media files')
- media_file_paths.sort(key=lambda file_path: get_locale_key(file_path.name))
+ media_file_paths.sort(key=lambda file_path: get_natural_key(file_path.name))
if media_type == MediaType.Audio:
extension = self.media_controller.audio_extensions_list
else:
diff --git a/openlp/plugins/media/mediaplugin.py b/openlp/plugins/media/mediaplugin.py
index 5efe8c910..461e78746 100644
--- a/openlp/plugins/media/mediaplugin.py
+++ b/openlp/plugins/media/mediaplugin.py
@@ -78,10 +78,10 @@ class MediaPlugin(Plugin):
"""
log.debug('check_installed Mediainfo')
# Try to find mediainfo in the path
- exists = process_check_binary('mediainfo')
+ exists = process_check_binary(Path('mediainfo'))
# If mediainfo is not in the path, try to find it in the application folder
if not exists:
- exists = process_check_binary(os.path.join(str(AppLocation.get_directory(AppLocation.AppDir)), 'mediainfo'))
+ exists = process_check_binary(AppLocation.get_directory(AppLocation.AppDir) / 'mediainfo')
return exists
def app_startup(self):
@@ -165,10 +165,11 @@ def process_check_binary(program_path):
"""
Function that checks whether a binary MediaInfo is present
- :param program_path:The full path to the binary to check.
+ :param openlp.core.common.path.Path program_path:The full path to the binary to check.
:return: If exists or not
+ :rtype: bool
"""
- runlog = check_binary_exists(Path(program_path))
+ runlog = check_binary_exists(program_path)
# Analyse the output to see it the program is mediainfo
for line in runlog.splitlines():
decoded_line = line.decode()
diff --git a/openlp/plugins/presentations/lib/mediaitem.py b/openlp/plugins/presentations/lib/mediaitem.py
index b801597b1..923953040 100644
--- a/openlp/plugins/presentations/lib/mediaitem.py
+++ b/openlp/plugins/presentations/lib/mediaitem.py
@@ -23,7 +23,7 @@ import logging
from PyQt5 import QtCore, QtGui, QtWidgets
-from openlp.core.common.i18n import UiStrings, translate, get_locale_key
+from openlp.core.common.i18n import UiStrings, translate, get_natural_key
from openlp.core.common.path import Path, path_to_str, str_to_path
from openlp.core.common.registry import Registry
from openlp.core.common.settings import Settings
@@ -165,7 +165,7 @@ class PresentationMediaItem(MediaManagerItem):
if not initial_load:
self.main_window.display_progress_bar(len(file_paths))
# Sort the presentations by its filename considering language specific characters.
- file_paths.sort(key=lambda file_path: get_locale_key(file_path.name))
+ file_paths.sort(key=lambda file_path: get_natural_key(file_path.name))
for file_path in file_paths:
if not initial_load:
self.main_window.increment_progress_bar()
@@ -198,10 +198,10 @@ class PresentationMediaItem(MediaManagerItem):
if not (preview_path and preview_path.exists()):
icon = build_icon(':/general/general_delete.png')
else:
- if validate_thumb(Path(preview_path), Path(thumbnail_path)):
+ if validate_thumb(preview_path, thumbnail_path):
icon = build_icon(thumbnail_path)
else:
- icon = create_thumb(str(preview_path), str(thumbnail_path))
+ icon = create_thumb(preview_path, thumbnail_path)
else:
if initial_load:
icon = build_icon(':/general/general_delete.png')
@@ -243,7 +243,7 @@ class PresentationMediaItem(MediaManagerItem):
"""
Clean up the files created such as thumbnails
- :param openlp.core.common.path.Path file_path: File path of the presention to clean up after
+ :param openlp.core.common.path.Path file_path: File path of the presentation to clean up after
:param bool clean_for_update: Only clean thumbnails if update is needed
:rtype: None
"""
diff --git a/openlp/plugins/presentations/lib/messagelistener.py b/openlp/plugins/presentations/lib/messagelistener.py
index 2bfd1b3ed..33f5e0693 100644
--- a/openlp/plugins/presentations/lib/messagelistener.py
+++ b/openlp/plugins/presentations/lib/messagelistener.py
@@ -191,7 +191,7 @@ class Controller(object):
"""
Based on the handler passed at startup triggers the previous slide event.
"""
- log.debug('Live = {live}, previous'.formta(live=self.is_live))
+ log.debug('Live = {live}, previous'.format(live=self.is_live))
if not self.doc:
return
if not self.is_live:
diff --git a/openlp/plugins/presentations/lib/pdfcontroller.py b/openlp/plugins/presentations/lib/pdfcontroller.py
index 26eb87d85..715e4e3e7 100644
--- a/openlp/plugins/presentations/lib/pdfcontroller.py
+++ b/openlp/plugins/presentations/lib/pdfcontroller.py
@@ -19,8 +19,6 @@
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
-
-import os
import logging
import re
from subprocess import check_output, CalledProcessError
diff --git a/openlp/plugins/presentations/lib/pptviewcontroller.py b/openlp/plugins/presentations/lib/pptviewcontroller.py
index 0a403df37..ddabe07e1 100644
--- a/openlp/plugins/presentations/lib/pptviewcontroller.py
+++ b/openlp/plugins/presentations/lib/pptviewcontroller.py
@@ -70,7 +70,7 @@ class PptviewController(PresentationController):
try:
self.start_process()
return self.process.CheckInstalled()
- except WindowsError:
+ except OSError:
return False
def start_process(self):
diff --git a/openlp/plugins/presentations/lib/presentationcontroller.py b/openlp/plugins/presentations/lib/presentationcontroller.py
index dd099c130..b54d8afa9 100644
--- a/openlp/plugins/presentations/lib/presentationcontroller.py
+++ b/openlp/plugins/presentations/lib/presentationcontroller.py
@@ -25,7 +25,7 @@ from PyQt5 import QtCore
from openlp.core.common import md5_hash
from openlp.core.common.applocation import AppLocation
-from openlp.core.common.path import Path, create_paths, rmtree
+from openlp.core.common.path import Path, create_paths
from openlp.core.common.registry import Registry
from openlp.core.common.settings import Settings
from openlp.core.lib import create_thumb, validate_thumb
@@ -126,9 +126,9 @@ class PresentationDocument(object):
thumbnail_folder_path = self.get_thumbnail_folder()
temp_folder_path = self.get_temp_folder()
if thumbnail_folder_path.exists():
- rmtree(thumbnail_folder_path)
+ thumbnail_folder_path.rmtree()
if temp_folder_path.exists():
- rmtree(temp_folder_path)
+ temp_folder_path.rmtree()
except OSError:
log.exception('Failed to delete presentation controller files')
@@ -139,7 +139,8 @@ class PresentationDocument(object):
:return: The path to the thumbnail
:rtype: openlp.core.common.path.Path
"""
- # TODO: If statement can be removed when the upgrade path from 2.0.x to 2.2.x is no longer needed
+ # TODO: Can be removed when the upgrade path to OpenLP 3.0 is no longer needed, also ensure code in
+ # get_temp_folder and PresentationPluginapp_startup is removed
if Settings().value('presentations/thumbnail_scheme') == 'md5':
folder = md5_hash(bytes(self.file_path))
else:
@@ -153,7 +154,8 @@ class PresentationDocument(object):
:return: The path to the temporary file folder
:rtype: openlp.core.common.path.Path
"""
- # TODO: If statement can be removed when the upgrade path from 2.0.x to 2.2.x is no longer needed
+ # TODO: Can be removed when the upgrade path to OpenLP 3.0 is no longer needed, also ensure code in
+ # get_thumbnail_folder and PresentationPluginapp_startup is removed
if Settings().value('presentations/thumbnail_scheme') == 'md5':
folder = md5_hash(bytes(self.file_path))
else:
@@ -265,7 +267,7 @@ class PresentationDocument(object):
return
if image_path.is_file():
thumb_path = self.get_thumbnail_path(index, False)
- create_thumb(str(image_path), str(thumb_path), False, QtCore.QSize(-1, 360))
+ create_thumb(image_path, thumb_path, False, QtCore.QSize(-1, 360))
def get_thumbnail_path(self, slide_no, check_exists=False):
"""
diff --git a/openlp/plugins/presentations/presentationplugin.py b/openlp/plugins/presentations/presentationplugin.py
index 7f3333049..a68a22176 100644
--- a/openlp/plugins/presentations/presentationplugin.py
+++ b/openlp/plugins/presentations/presentationplugin.py
@@ -31,6 +31,7 @@ from PyQt5 import QtCore
from openlp.core.api.http import register_endpoint
from openlp.core.common import extension_loader
from openlp.core.common.i18n import translate
+from openlp.core.common.settings import Settings
from openlp.core.lib import Plugin, StringContent, build_icon
from openlp.plugins.presentations.endpoint import api_presentations_endpoint, presentations_endpoint
from openlp.plugins.presentations.lib import PresentationController, PresentationMediaItem, PresentationTab
@@ -136,6 +137,20 @@ class PresentationPlugin(Plugin):
self.register_controllers(controller)
return bool(self.controllers)
+ def app_startup(self):
+ """
+ Perform tasks on application startup.
+ """
+ # TODO: Can be removed when the upgrade path to OpenLP 3.0 is no longer needed, also ensure code in
+ # PresentationDocument.get_thumbnail_folder and PresentationDocument.get_temp_folder is removed
+ super().app_startup()
+ presentation_paths = Settings().value('presentations/presentations files')
+ for path in presentation_paths:
+ self.media_item.clean_up_thumbnails(path, clean_for_update=True)
+ self.media_item.list_view.clear()
+ Settings().setValue('presentations/thumbnail_scheme', 'md5')
+ self.media_item.validate_and_load(presentation_paths)
+
@staticmethod
def about():
"""
diff --git a/openlp/plugins/songs/forms/editsongform.py b/openlp/plugins/songs/forms/editsongform.py
index fa475a63f..6e0772418 100644
--- a/openlp/plugins/songs/forms/editsongform.py
+++ b/openlp/plugins/songs/forms/editsongform.py
@@ -105,9 +105,9 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
self.topics_list_view.setSortingEnabled(False)
self.topics_list_view.setAlternatingRowColors(True)
self.audio_list_widget.setAlternatingRowColors(True)
- self.find_verse_split = re.compile('---\[\]---\n', re.UNICODE)
- self.whitespace = re.compile(r'\W+', re.UNICODE)
- self.find_tags = re.compile(u'\{/?\w+\}', re.UNICODE)
+ self.find_verse_split = re.compile('---\[\]---\n')
+ self.whitespace = re.compile(r'\W+')
+ self.find_tags = re.compile(r'\{/?\w+\}')
def _load_objects(self, cls, combo, cache):
"""
diff --git a/openlp/plugins/songs/lib/__init__.py b/openlp/plugins/songs/lib/__init__.py
index cce70a03d..c6095e122 100644
--- a/openlp/plugins/songs/lib/__init__.py
+++ b/openlp/plugins/songs/lib/__init__.py
@@ -24,7 +24,6 @@ The :mod:`~openlp.plugins.songs.lib` module contains a number of library functio
"""
import logging
-import os
import re
from PyQt5 import QtWidgets
@@ -39,8 +38,8 @@ from openlp.plugins.songs.lib.ui import SongStrings
log = logging.getLogger(__name__)
-WHITESPACE = re.compile(r'[\W_]+', re.UNICODE)
-APOSTROPHE = re.compile('[\'`’ʻ′]', re.UNICODE)
+WHITESPACE = re.compile(r'[\W_]+')
+APOSTROPHE = re.compile(r'[\'`’ʻ′]')
# PATTERN will look for the next occurence of one of these symbols:
# \controlword - optionally preceded by \*, optionally followed by a number
# \'## - where ## is a pair of hex digits, representing a single character
diff --git a/openlp/plugins/songs/lib/importers/easyslides.py b/openlp/plugins/songs/lib/importers/easyslides.py
index a1ffb7b7c..6d717bdb4 100644
--- a/openlp/plugins/songs/lib/importers/easyslides.py
+++ b/openlp/plugins/songs/lib/importers/easyslides.py
@@ -25,6 +25,7 @@ import re
from lxml import etree, objectify
+from openlp.core.common import normalize_str
from openlp.plugins.songs.lib import VerseType
from openlp.plugins.songs.lib.importers.songimport import SongImport
@@ -225,7 +226,7 @@ class EasySlidesImport(SongImport):
verses[reg].setdefault(vt, {})
verses[reg][vt].setdefault(vn, {})
verses[reg][vt][vn].setdefault(inst, [])
- verses[reg][vt][vn][inst].append(self.tidy_text(line))
+ verses[reg][vt][vn][inst].append(normalize_str(line))
# done parsing
versetags = []
# we use our_verse_order to ensure, we insert lyrics in the same order
diff --git a/openlp/plugins/songs/lib/importers/mediashout.py b/openlp/plugins/songs/lib/importers/mediashout.py
index 67cf0d0fb..9df9baa0f 100644
--- a/openlp/plugins/songs/lib/importers/mediashout.py
+++ b/openlp/plugins/songs/lib/importers/mediashout.py
@@ -101,7 +101,7 @@ class MediaShoutImport(SongImport):
self.song_book_name = song.SongID
for verse in verses:
tag = VERSE_TAGS[verse.Type] + str(verse.Number) if verse.Type < len(VERSE_TAGS) else 'O'
- self.add_verse(self.tidy_text(verse.Text), tag)
+ self.add_verse(verse.Text, tag)
for order in verse_order:
if order.Type < len(VERSE_TAGS):
self.verse_order_list.append(VERSE_TAGS[order.Type] + str(order.Number))
diff --git a/openlp/plugins/songs/lib/importers/openoffice.py b/openlp/plugins/songs/lib/importers/openoffice.py
index a097d8b85..f2a8b2147 100644
--- a/openlp/plugins/songs/lib/importers/openoffice.py
+++ b/openlp/plugins/songs/lib/importers/openoffice.py
@@ -24,7 +24,7 @@ import time
from PyQt5 import QtCore
-from openlp.core.common import is_win, get_uno_command, get_uno_instance
+from openlp.core.common import get_uno_command, get_uno_instance, is_win, normalize_str
from openlp.core.common.i18n import translate
from .songimport import SongImport
@@ -241,7 +241,7 @@ class OpenOfficeImport(SongImport):
:param text: The text.
"""
- song_texts = self.tidy_text(text).split('\f')
+ song_texts = normalize_str(text).split('\f')
self.set_defaults()
for song_text in song_texts:
if song_text.strip():
diff --git a/openlp/plugins/songs/lib/importers/opensong.py b/openlp/plugins/songs/lib/importers/opensong.py
index e6924e7b2..6cd690562 100644
--- a/openlp/plugins/songs/lib/importers/opensong.py
+++ b/openlp/plugins/songs/lib/importers/opensong.py
@@ -25,6 +25,7 @@ import re
from lxml import objectify
from lxml.etree import Error, LxmlError
+from openlp.core.common import normalize_str
from openlp.core.common.i18n import translate
from openlp.core.common.settings import Settings
from openlp.plugins.songs.lib import VerseType
@@ -262,7 +263,7 @@ class OpenSongImport(SongImport):
post=this_line[offset + column:])
offset += len(chord) + 2
# Tidy text and remove the ____s from extended words
- this_line = self.tidy_text(this_line)
+ this_line = normalize_str(this_line)
this_line = this_line.replace('_', '')
this_line = this_line.replace('||', '\n[---]\n')
this_line = this_line.strip()
diff --git a/openlp/plugins/songs/lib/importers/songimport.py b/openlp/plugins/songs/lib/importers/songimport.py
index a67c17fe7..2bd8c0e56 100644
--- a/openlp/plugins/songs/lib/importers/songimport.py
+++ b/openlp/plugins/songs/lib/importers/songimport.py
@@ -25,6 +25,7 @@ import re
from PyQt5 import QtCore
+from openlp.core.common import normalize_str
from openlp.core.common.applocation import AppLocation
from openlp.core.common.i18n import translate
from openlp.core.common.path import copyfile, create_paths
@@ -130,26 +131,6 @@ class SongImport(QtCore.QObject):
def register(self, import_wizard):
self.import_wizard = import_wizard
- def tidy_text(self, text):
- """
- Get rid of some dodgy unicode and formatting characters we're not interested in. Some can be converted to ascii.
- """
- text = text.replace('\u2018', '\'')
- text = text.replace('\u2019', '\'')
- text = text.replace('\u201c', '"')
- text = text.replace('\u201d', '"')
- text = text.replace('\u2026', '...')
- text = text.replace('\u2013', '-')
- text = text.replace('\u2014', '-')
- # Replace vertical tab with 2 linebreaks
- text = text.replace('\v', '\n\n')
- # Replace form feed (page break) with 2 linebreaks
- text = text.replace('\f', '\n\n')
- # Remove surplus blank lines, spaces, trailing/leading spaces
- text = re.sub(r'[ \t]+', ' ', text)
- text = re.sub(r' ?(\r\n?|\n) ?', '\n', text)
- return text
-
def process_song_text(self, text):
"""
Process the song text from import
@@ -368,7 +349,7 @@ class SongImport(QtCore.QObject):
verse_tag = VerseType.tags[VerseType.Other]
log.info('Versetype {old} changing to {new}'.format(old=verse_def, new=new_verse_def))
verse_def = new_verse_def
- sxml.add_verse_to_lyrics(verse_tag, verse_def[1:], verse_text, lang)
+ sxml.add_verse_to_lyrics(verse_tag, verse_def[1:], normalize_str(verse_text), lang)
song.lyrics = str(sxml.extract_xml(), 'utf-8')
if not self.verse_order_list and self.verse_order_list_generated_useful:
self.verse_order_list = self.verse_order_list_generated
diff --git a/openlp/plugins/songs/lib/importers/songsoffellowship.py b/openlp/plugins/songs/lib/importers/songsoffellowship.py
index 13e073cc1..bbba654c9 100644
--- a/openlp/plugins/songs/lib/importers/songsoffellowship.py
+++ b/openlp/plugins/songs/lib/importers/songsoffellowship.py
@@ -194,7 +194,6 @@ class SongsOfFellowshipImport(OpenOfficeImport):
:param text_portion: A Piece of text
"""
text = text_portion.getString()
- text = self.tidy_text(text)
if text.strip() == '':
return text
if text_portion.CharWeight == BOLD:
diff --git a/openlp/plugins/songs/lib/importers/zionworx.py b/openlp/plugins/songs/lib/importers/zionworx.py
index 5cfc0576d..23817c31a 100644
--- a/openlp/plugins/songs/lib/importers/zionworx.py
+++ b/openlp/plugins/songs/lib/importers/zionworx.py
@@ -30,9 +30,6 @@ from openlp.plugins.songs.lib.importers.songimport import SongImport
log = logging.getLogger(__name__)
-# Used to strip control chars (except 10=LF, 13=CR)
-CONTROL_CHARS_MAP = dict.fromkeys(list(range(10)) + [11, 12] + list(range(14, 32)) + [127])
-
class ZionWorxImport(SongImport):
"""
@@ -95,12 +92,12 @@ class ZionWorxImport(SongImport):
return
self.set_defaults()
try:
- self.title = self._decode(record['Title1'])
+ self.title = record['Title1']
if record['Title2']:
- self.alternate_title = self._decode(record['Title2'])
- self.parse_author(self._decode(record['Writer']))
- self.add_copyright(self._decode(record['Copyright']))
- lyrics = self._decode(record['Lyrics'])
+ self.alternate_title = record['Title2']
+ self.parse_author(record['Writer'])
+ self.add_copyright(record['Copyright'])
+ lyrics = record['Lyrics']
except UnicodeDecodeError as e:
self.log_error(translate('SongsPlugin.ZionWorxImport', 'Record {index}').format(index=index),
translate('SongsPlugin.ZionWorxImport', 'Decoding error: {error}').format(error=e))
@@ -122,10 +119,3 @@ class ZionWorxImport(SongImport):
if not self.finish():
self.log_error(translate('SongsPlugin.ZionWorxImport', 'Record %d') % index +
(': "' + title + '"' if title else ''))
-
- def _decode(self, str):
- """
- Strips all control characters (except new lines).
- """
- # ZionWorx has no option for setting the encoding for its songs, so we assume encoding is always the same.
- return str.translate(CONTROL_CHARS_MAP)
diff --git a/openlp/plugins/songs/lib/openlyricsxml.py b/openlp/plugins/songs/lib/openlyricsxml.py
index 74d91068c..ef47fa77b 100644
--- a/openlp/plugins/songs/lib/openlyricsxml.py
+++ b/openlp/plugins/songs/lib/openlyricsxml.py
@@ -281,7 +281,7 @@ class OpenLyrics(object):
# Process the formatting tags.
# Have we any tags in song lyrics?
tags_element = None
- match = re.search('\{/?\w+\}', song.lyrics, re.UNICODE)
+ match = re.search(r'\{/?\w+\}', song.lyrics)
if match:
# Named 'format_' - 'format' is built-in function in Python.
format_ = etree.SubElement(song_xml, 'format')
diff --git a/openlp/plugins/songusage/forms/songusagedetailform.py b/openlp/plugins/songusage/forms/songusagedetailform.py
index 147e26d10..dc3393c8d 100644
--- a/openlp/plugins/songusage/forms/songusagedetailform.py
+++ b/openlp/plugins/songusage/forms/songusagedetailform.py
@@ -54,8 +54,14 @@ class SongUsageDetailForm(QtWidgets.QDialog, Ui_SongUsageDetailDialog, RegistryP
"""
We need to set up the screen
"""
- self.from_date_calendar.setSelectedDate(Settings().value(self.plugin.settings_section + '/from date'))
- self.to_date_calendar.setSelectedDate(Settings().value(self.plugin.settings_section + '/to date'))
+ to_date = Settings().value(self.plugin.settings_section + '/to date')
+ if not (isinstance(to_date, QtCore.QDate) and to_date.isValid()):
+ to_date = QtCore.QDate.currentDate()
+ from_date = Settings().value(self.plugin.settings_section + '/from date')
+ if not (isinstance(from_date, QtCore.QDate) and from_date.isValid()):
+ from_date = to_date.addYears(-1)
+ self.from_date_calendar.setSelectedDate(from_date)
+ self.to_date_calendar.setSelectedDate(to_date)
self.report_path_edit.path = Settings().value(self.plugin.settings_section + '/last directory export')
def on_report_path_edit_path_changed(self, file_path):
diff --git a/openlp/plugins/songusage/songusageplugin.py b/openlp/plugins/songusage/songusageplugin.py
index a9acd791a..79f21a8cf 100644
--- a/openlp/plugins/songusage/songusageplugin.py
+++ b/openlp/plugins/songusage/songusageplugin.py
@@ -38,20 +38,17 @@ from openlp.plugins.songusage.lib.db import init_schema, SongUsageItem
log = logging.getLogger(__name__)
-YEAR = QtCore.QDate().currentDate().year()
-if QtCore.QDate().currentDate().month() < 9:
- YEAR -= 1
-
+TODAY = QtCore.QDate.currentDate()
__default_settings__ = {
'songusage/db type': 'sqlite',
'songusage/db username': '',
- 'songuasge/db password': '',
- 'songuasge/db hostname': '',
- 'songuasge/db database': '',
+ 'songusage/db password': '',
+ 'songusage/db hostname': '',
+ 'songusage/db database': '',
'songusage/active': False,
- 'songusage/to date': QtCore.QDate(YEAR, 8, 31),
- 'songusage/from date': QtCore.QDate(YEAR - 1, 9, 1),
+ 'songusage/to date': TODAY,
+ 'songusage/from date': TODAY.addYears(-1),
'songusage/last directory export': None
}
diff --git a/scripts/jenkins_script.py b/scripts/jenkins_script.py
index 9a0e9a4ec..a669de71e 100755
--- a/scripts/jenkins_script.py
+++ b/scripts/jenkins_script.py
@@ -21,35 +21,35 @@
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
-This script helps to trigger builds of branches. To use it you have to install the jenkins-webapi package:
+This script helps to trigger builds of branches. To use it you have to install the python-jenkins module. On Fedora
+and Ubuntu/Debian, it is available as the ``python3-jenkins`` package::
- pip3 install jenkins-webapi
+ $ sudo dnf/apt install python3-jenkins
-You probably want to create an alias. Add this to your ~/.bashrc file and then logout and login (to apply the alias):
+To make it easier to run you may want to create a shell script or an alias. To create an alias, add this to your
+``~/.bashrc`` (or ``~/.zshrc``) file and then log out and log back in again (to apply the alias)::
- alias ci="python3 ./scripts/jenkins_script.py TOKEN"
+ alias ci="python3 /path/to/openlp_root/scripts/jenkins_script.py -u USERNAME -p PASSWORD"
-You can look up the token in the Branch-01-Pull job configuration or ask in IRC.
+To create a shell script, create the following file in a location in your ``$PATH`` (I called mine ``ci``)::
+
+ #!/bin/bash
+ python3 /path/to/openlp_root/scripts/jenkins_script.py -u USERNAME -p PASSWORD
+
+``USERNAME`` is your Jenkins username, and ``PASSWORD`` is your Jenkins password or personal token.
+
+An older version of this script used to use a shared TOKEN, but this has been replaced with the username and password.
"""
-
+import os
import re
-import sys
import time
-from optparse import OptionParser
+from argparse import ArgumentParser
from subprocess import Popen, PIPE
-import warnings
-from requests.exceptions import HTTPError
from jenkins import Jenkins
-
JENKINS_URL = 'https://ci.openlp.io/'
REPO_REGEX = r'(.*/+)(~.*)'
-# Allows us to black list token. So when we change the token, we can display a proper message to the user.
-OLD_TOKENS = []
-
-# Disable the InsecureRequestWarning we get from urllib3, because we're not verifying our own self-signed certificate
-warnings.simplefilter('ignore')
class OpenLPJobs(object):
@@ -85,13 +85,23 @@ class JenkinsTrigger(object):
:param token: The token we need to trigger the build. If you do not have this token, ask in IRC.
"""
- def __init__(self, token):
+ def __init__(self, username, password, can_use_colour):
"""
Create the JenkinsTrigger instance.
"""
- self.token = token
+ self.jobs = {}
+ self.can_use_colour = can_use_colour and not os.name.startswith('nt')
self.repo_name = get_repo_name()
- self.jenkins_instance = Jenkins(JENKINS_URL)
+ self.server = Jenkins(JENKINS_URL, username=username, password=password)
+
+ def fetch_jobs(self):
+ """
+ Get the job info for all the jobs
+ """
+ for job_name in OpenLPJobs.Jobs:
+ job_info = self.server.get_job_info(job_name)
+ self.jobs[job_name] = job_info
+ self.jobs[job_name]['nextBuildUrl'] = '{url}{nextBuildNumber}/'.format(**job_info)
def trigger_build(self):
"""
@@ -102,15 +112,15 @@ class JenkinsTrigger(object):
# We just want the name (not the email).
name = ' '.join(raw_output.decode().split()[:-1])
cause = 'Build triggered by %s (%s)' % (name, self.repo_name)
- self.jenkins_instance.job(OpenLPJobs.Branch_Pull).build({'BRANCH_NAME': self.repo_name, 'cause': cause},
- token=self.token)
+ self.fetch_jobs()
+ self.server.build_job(OpenLPJobs.Branch_Pull, {'BRANCH_NAME': self.repo_name, 'cause': cause})
def print_output(self):
"""
Print the status information of the build triggered.
"""
print('Add this to your merge proposal:')
- print('--------------------------------')
+ print('-' * 80)
bzr = Popen(('bzr', 'revno'), stdout=PIPE, stderr=PIPE)
raw_output, error = bzr.communicate()
revno = raw_output.decode().strip()
@@ -118,7 +128,10 @@ class JenkinsTrigger(object):
for job in OpenLPJobs.Jobs:
if not self.__print_build_info(job):
- print('Stopping after failure')
+ if self.current_build:
+ print('Stopping after failure, see {}console for more details'.format(self.current_build['url']))
+ else:
+ print('Stopping after failure')
break
def open_browser(self):
@@ -129,6 +142,20 @@ class JenkinsTrigger(object):
# Open the url
Popen(('xdg-open', url), stderr=PIPE)
+ def _get_build_info(self, job_name, build_number):
+ """
+ Get the build info from the server. This method will check the queue and wait for the build.
+ """
+ queue_info = self.server.get_queue_info()
+ tries = 0
+ while queue_info and tries < 50:
+ tries += 1
+ time.sleep(0.5)
+ queue_info = self.server.get_queue_info()
+ if tries >= 50:
+ raise Exception('Build has not started yet, it may be stuck in the queue.')
+ return self.server.get_build_info(job_name, build_number)
+
def __print_build_info(self, job_name):
"""
This helper method prints the job information of the given ``job_name``
@@ -136,21 +163,24 @@ class JenkinsTrigger(object):
:param job_name: The name of the job we want the information from. For example *Branch-01-Pull*. Use the class
variables from the :class:`OpenLPJobs` class.
"""
+ job = self.jobs[job_name]
+ print('{:<70} [WAITING]'.format(job['nextBuildUrl']), end='', flush=True)
+ self.current_build = self._get_build_info(job_name, job['nextBuildNumber'])
+ print('\b\b\b\b\b\b\b\b\b[RUNNING]', end='', flush=True)
is_success = False
- job = self.jenkins_instance.job(job_name)
- while job.info['inQueue']:
- time.sleep(1)
- build = job.last_build
- build.wait()
- if build.info['result'] == 'SUCCESS':
- # Make 'SUCCESS' green.
- result_string = '%s%s%s' % (Colour.GREEN_START, build.info['result'], Colour.GREEN_END)
- is_success = True
- else:
- # Make 'FAILURE' red.
- result_string = '%s%s%s' % (Colour.RED_START, build.info['result'], Colour.RED_END)
- url = build.info['url']
- print('[%s] %s' % (result_string, url))
+ while self.current_build['building'] is True:
+ time.sleep(0.5)
+ self.current_build = self.server.get_build_info(job_name, job['nextBuildNumber'])
+ result_string = self.current_build['result']
+ is_success = result_string == 'SUCCESS'
+ if self.can_use_colour:
+ if is_success:
+ # Make 'SUCCESS' green.
+ result_string = '{}{}{}'.format(Colour.GREEN_START, result_string, Colour.GREEN_END)
+ else:
+ # Make 'FAILURE' red.
+ result_string = '{}{}{}'.format(Colour.RED_START, result_string, Colour.RED_END)
+ print('\b\b\b\b\b\b\b\b\b[{:>7}]'.format(result_string))
return is_success
@@ -186,36 +216,29 @@ def get_repo_name():
def main():
- usage = 'Usage: python %prog TOKEN [options]'
+ """
+ Run the script
+ """
+ parser = ArgumentParser()
+ parser.add_argument('-d', '--disable-output', action='store_true', default=False, help='Disable output')
+ parser.add_argument('-b', '--open-browser', action='store_true', default=False,
+ help='Opens the jenkins page in your browser')
+ parser.add_argument('-n', '--no-colour', action='store_true', default=False,
+ help='Disable coloured output (always disabled on Windows)')
+ parser.add_argument('-u', '--username', required=True, help='Your Jenkins username')
+ parser.add_argument('-p', '--password', required=True, help='Your Jenkins password or personal token')
+ args = parser.parse_args()
- parser = OptionParser(usage=usage)
- parser.add_option('-d', '--disable-output', dest='enable_output', action='store_false', default=True,
- help='Disable output.')
- parser.add_option('-b', '--open-browser', dest='open_browser', action='store_true', default=False,
- help='Opens the jenkins page in your browser.')
- options, args = parser.parse_args(sys.argv)
-
- if len(args) == 2:
- if not get_repo_name():
- print('Not a branch. Have you pushed it to launchpad? Did you cd to the branch?')
- return
- token = args[-1]
- if token in OLD_TOKENS:
- print('Your token is not valid anymore. Get the most recent one.')
- return
- jenkins_trigger = JenkinsTrigger(token)
- try:
- jenkins_trigger.trigger_build()
- except HTTPError:
- print('Wrong token.')
- return
- # Open the browser before printing the output.
- if options.open_browser:
- jenkins_trigger.open_browser()
- if options.enable_output:
- jenkins_trigger.print_output()
- else:
- parser.print_help()
+ if not get_repo_name():
+ print('Not a branch. Have you pushed it to launchpad? Did you cd to the branch?')
+ return
+ jenkins_trigger = JenkinsTrigger(args.username, args.password, not args.no_colour)
+ jenkins_trigger.trigger_build()
+ # Open the browser before printing the output.
+ if args.open_browser:
+ jenkins_trigger.open_browser()
+ if not args.disable_output:
+ jenkins_trigger.print_output()
if __name__ == '__main__':
diff --git a/tests/functional/openlp_core/api/test_deploy.py b/tests/functional/openlp_core/api/test_deploy.py
index be36fb9c7..702e2a35d 100644
--- a/tests/functional/openlp_core/api/test_deploy.py
+++ b/tests/functional/openlp_core/api/test_deploy.py
@@ -19,15 +19,13 @@
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
-
-import os
-import shutil
from tempfile import mkdtemp
from unittest import TestCase
from openlp.core.api.deploy import deploy_zipfile
+from openlp.core.common.path import Path, copyfile
-TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..', 'resources'))
+TEST_PATH = (Path(__file__).parent / '..' / '..' / '..' / 'resources').resolve()
class TestRemoteDeploy(TestCase):
@@ -39,25 +37,25 @@ class TestRemoteDeploy(TestCase):
"""
Setup for tests
"""
- self.app_root = mkdtemp()
+ self.app_root_path = Path(mkdtemp())
def tearDown(self):
"""
Clean up after tests
"""
- shutil.rmtree(self.app_root)
+ self.app_root_path.rmtree()
def test_deploy_zipfile(self):
"""
Remote Deploy tests - test the dummy zip file is processed correctly
"""
# GIVEN: A new downloaded zip file
- aa = TEST_PATH
- zip_file = os.path.join(TEST_PATH, 'remotes', 'site.zip')
- app_root = os.path.join(self.app_root, 'site.zip')
- shutil.copyfile(zip_file, app_root)
- # WHEN: I process the zipfile
- deploy_zipfile(self.app_root, 'site.zip')
+ zip_path = TEST_PATH / 'remotes' / 'site.zip'
+ app_root_path = self.app_root_path / 'site.zip'
+ copyfile(zip_path, app_root_path)
- # THEN test if www directory has been created
- self.assertTrue(os.path.isdir(os.path.join(self.app_root, 'www')), 'We should have a www directory')
+ # WHEN: I process the zipfile
+ deploy_zipfile(self.app_root_path, 'site.zip')
+
+ # THEN: test if www directory has been created
+ self.assertTrue((self.app_root_path / 'www').is_dir(), 'We should have a www directory')
diff --git a/tests/functional/openlp_core/common/test_actions.py b/tests/functional/openlp_core/common/test_actions.py
index bd59d6577..57905654d 100644
--- a/tests/functional/openlp_core/common/test_actions.py
+++ b/tests/functional/openlp_core/common/test_actions.py
@@ -153,6 +153,7 @@ class TestActionList(TestCase, TestMixin):
"""
Prepare the tests
"""
+ self.setup_application()
self.action_list = ActionList.get_instance()
self.build_settings()
self.settings = Settings()
diff --git a/tests/functional/openlp_core/common/test_httputils.py b/tests/functional/openlp_core/common/test_httputils.py
index e620fa04e..5e7a396b2 100644
--- a/tests/functional/openlp_core/common/test_httputils.py
+++ b/tests/functional/openlp_core/common/test_httputils.py
@@ -233,7 +233,7 @@ class TestHttpUtils(TestCase, TestMixin):
Test socket timeout gets caught
"""
# GIVEN: Mocked urlopen to fake a network disconnect in the middle of a download
- mocked_requests.get.side_effect = IOError
+ mocked_requests.get.side_effect = OSError
# WHEN: Attempt to retrieve a file
url_get_file(MagicMock(), url='http://localhost/test', file_path=Path(self.tempfile))
diff --git a/tests/functional/openlp_core/common/test_i18n.py b/tests/functional/openlp_core/common/test_i18n.py
index d6828fb6f..bffb819dc 100644
--- a/tests/functional/openlp_core/common/test_i18n.py
+++ b/tests/functional/openlp_core/common/test_i18n.py
@@ -155,7 +155,7 @@ def test_check_same_instance():
assert first_instance is second_instance, 'Two UiStrings objects should be the same instance'
-def test_translate(self):
+def test_translate():
"""
Test the translate() function
"""
diff --git a/tests/functional/openlp_core/common/test_init.py b/tests/functional/openlp_core/common/test_init.py
index 5acfe59bb..9b86c99aa 100644
--- a/tests/functional/openlp_core/common/test_init.py
+++ b/tests/functional/openlp_core/common/test_init.py
@@ -28,7 +28,7 @@ from unittest import TestCase
from unittest.mock import MagicMock, PropertyMock, call, patch
from openlp.core.common import add_actions, clean_filename, delete_file, get_file_encoding, get_filesystem_encoding, \
- get_uno_command, get_uno_instance, split_filename
+ get_uno_command, get_uno_instance
from openlp.core.common.path import Path
from tests.helpers.testmixin import TestMixin
@@ -236,47 +236,6 @@ class TestInit(TestCase, TestMixin):
mocked_getdefaultencoding.assert_called_with()
self.assertEqual('utf-8', result, 'The result should be "utf-8"')
- def test_split_filename_with_file_path(self):
- """
- Test the split_filename() function with a path to a file
- """
- # GIVEN: A path to a file.
- if os.name == 'nt':
- file_path = 'C:\\home\\user\\myfile.txt'
- wanted_result = ('C:\\home\\user', 'myfile.txt')
- else:
- file_path = '/home/user/myfile.txt'
- wanted_result = ('/home/user', 'myfile.txt')
- with patch('openlp.core.common.os.path.isfile') as mocked_is_file:
- mocked_is_file.return_value = True
-
- # WHEN: Split the file name.
- result = split_filename(file_path)
-
- # THEN: A tuple should be returned.
- self.assertEqual(wanted_result, result, 'A tuple with the dir and file name should have been returned')
-
- def test_split_filename_with_dir_path(self):
- """
- Test the split_filename() function with a path to a directory
- """
- # GIVEN: A path to a dir.
- if os.name == 'nt':
- file_path = 'C:\\home\\user\\mydir'
- wanted_result = ('C:\\home\\user\\mydir', '')
- else:
- file_path = '/home/user/mydir'
- wanted_result = ('/home/user/mydir', '')
- with patch('openlp.core.common.os.path.isfile') as mocked_is_file:
- mocked_is_file.return_value = False
-
- # WHEN: Split the file name.
- result = split_filename(file_path)
-
- # THEN: A tuple should be returned.
- self.assertEqual(wanted_result, result,
- 'A two-entry tuple with the directory and file name (empty) should have been returned.')
-
def test_clean_filename(self):
"""
Test the clean_filename() function
diff --git a/tests/functional/openlp_core/common/test_path.py b/tests/functional/openlp_core/common/test_path.py
index 4b30bd2cb..498b7aaa0 100644
--- a/tests/functional/openlp_core/common/test_path.py
+++ b/tests/functional/openlp_core/common/test_path.py
@@ -26,7 +26,7 @@ import os
from unittest import TestCase
from unittest.mock import ANY, MagicMock, patch
-from openlp.core.common.path import Path, copy, copyfile, copytree, create_paths, path_to_str, replace_params, rmtree, \
+from openlp.core.common.path import Path, copy, copyfile, copytree, create_paths, path_to_str, replace_params, \
str_to_path, which
@@ -172,31 +172,35 @@ class TestShutil(TestCase):
"""
Test :func:`rmtree`
"""
- # GIVEN: A mocked :func:`shutil.rmtree`
+ # GIVEN: A mocked :func:`shutil.rmtree` and a test Path object
with patch('openlp.core.common.path.shutil.rmtree', return_value=None) as mocked_shutil_rmtree:
+ path = Path('test', 'path')
# WHEN: Calling :func:`openlp.core.common.path.rmtree` with the path parameter as Path object type
- result = rmtree(Path('test', 'path'))
+ result = path.rmtree()
# THEN: :func:`shutil.rmtree` should have been called with the str equivalents of the Path object.
- mocked_shutil_rmtree.assert_called_once_with(os.path.join('test', 'path'))
+ mocked_shutil_rmtree.assert_called_once_with(
+ os.path.join('test', 'path'), False, None)
self.assertIsNone(result)
def test_rmtree_optional_params(self):
"""
Test :func:`openlp.core.common.path.rmtree` when optional parameters are passed
"""
- # GIVEN: A mocked :func:`shutil.rmtree`
- with patch('openlp.core.common.path.shutil.rmtree', return_value='') as mocked_shutil_rmtree:
+ # GIVEN: A mocked :func:`shutil.rmtree` and a test Path object.
+ with patch('openlp.core.common.path.shutil.rmtree', return_value=None) as mocked_shutil_rmtree:
+ path = Path('test', 'path')
mocked_on_error = MagicMock()
# WHEN: Calling :func:`openlp.core.common.path.rmtree` with :param:`ignore_errors` set to True and
# :param:`onerror` set to a mocked object
- rmtree(Path('test', 'path'), ignore_errors=True, onerror=mocked_on_error)
+ path.rmtree(ignore_errors=True, onerror=mocked_on_error)
# THEN: :func:`shutil.rmtree` should have been called with the optional parameters, with out any of the
# values being modified
- mocked_shutil_rmtree.assert_called_once_with(ANY, ignore_errors=True, onerror=mocked_on_error)
+ mocked_shutil_rmtree.assert_called_once_with(
+ os.path.join('test', 'path'), True, mocked_on_error)
def test_which_no_command(self):
"""
@@ -371,13 +375,13 @@ class TestPath(TestCase):
@patch('openlp.core.common.path.log')
def test_create_paths_dir_io_error(self, mocked_logger):
"""
- Test the create_paths() when an IOError is raised
+ Test the create_paths() when an OSError is raised
"""
# GIVEN: A `Path` to check with patched out mkdir and exists methods
mocked_path = MagicMock()
- mocked_path.exists.side_effect = IOError('Cannot make directory')
+ mocked_path.exists.side_effect = OSError('Cannot make directory')
- # WHEN: An IOError is raised when checking the if the path exists.
+ # WHEN: An OSError is raised when checking the if the path exists.
create_paths(mocked_path)
# THEN: The Error should have been logged
@@ -385,7 +389,7 @@ class TestPath(TestCase):
def test_create_paths_dir_value_error(self):
"""
- Test the create_paths() when an error other than IOError is raised
+ Test the create_paths() when an error other than OSError is raised
"""
# GIVEN: A `Path` to check with patched out mkdir and exists methods
mocked_path = MagicMock()
diff --git a/tests/functional/openlp_core/common/test_settings.py b/tests/functional/openlp_core/common/test_settings.py
index d54a6a1e1..ff6ee8765 100644
--- a/tests/functional/openlp_core/common/test_settings.py
+++ b/tests/functional/openlp_core/common/test_settings.py
@@ -22,10 +22,12 @@
"""
Package to test the openlp.core.lib.settings package.
"""
+from pathlib import Path
from unittest import TestCase
-from unittest.mock import patch
+from unittest.mock import call, patch
-from openlp.core.common.settings import Settings
+from openlp.core.common import settings
+from openlp.core.common.settings import Settings, media_players_conv
from tests.helpers.testmixin import TestMixin
@@ -47,28 +49,58 @@ class TestSettings(TestCase, TestMixin):
"""
self.destroy_settings()
- def test_settings_basic(self):
- """
- Test the Settings creation and its default usage
- """
- # GIVEN: A new Settings setup
+ def test_media_players_conv(self):
+ """Test the media players conversion function"""
+ # GIVEN: A list of media players
+ media_players = 'phonon,webkit,vlc'
+
+ # WHEN: The media converter function is called
+ result = media_players_conv(media_players)
+
+ # THEN: The list should have been converted correctly
+ assert result == 'system,webkit,vlc'
+
+ def test_default_value(self):
+ """Test reading a setting that doesn't exist yet"""
+ # GIVEN: A setting that doesn't exist yet
# WHEN reading a setting for the first time
default_value = Settings().value('core/has run wizard')
# THEN the default value is returned
- self.assertFalse(default_value, 'The default value should be False')
+ assert default_value is False, 'The default value should be False'
+ def test_save_new_value(self):
+ """Test saving a new setting"""
+ # GIVEN: A setting that hasn't been saved yet
# WHEN a new value is saved into config
Settings().setValue('core/has run wizard', True)
# THEN the new value is returned when re-read
- self.assertTrue(Settings().value('core/has run wizard'), 'The saved value should have been returned')
+ assert Settings().value('core/has run wizard') is True, 'The saved value should have been returned'
+
+ def test_set_up_default_values(self):
+ """Test that the default values are updated"""
+ # GIVEN: A Settings object with defaults
+ # WHEN: set_up_default_values() is called
+ Settings.set_up_default_values()
+
+ # THEN: The default values should have been added to the dictionary
+ assert 'advanced/default service name' in Settings.__default_settings__
+
+ def test_get_default_value(self):
+ """Test that the default value for a setting is returned"""
+ # GIVEN: A Settings class with a default value
+ Settings.__default_settings__['test/moo'] = 'baa'
+
+ # WHEN: get_default_value() is called
+ result = Settings().get_default_value('test/moo')
+
+ # THEN: The correct default value should be returned
+ assert result == 'baa'
def test_settings_override(self):
- """
- Test the Settings creation and its override usage
- """
+ """Test the Settings creation and its override usage"""
# GIVEN: an override for the settings
screen_settings = {
'test/extend': 'very wide',
@@ -79,18 +111,22 @@ class TestSettings(TestCase, TestMixin):
extend = Settings().value('test/extend')
# THEN the default value is returned
- self.assertEqual('very wide', extend, 'The default value of "very wide" should be returned')
+ assert extend == 'very wide', 'The default value of "very wide" should be returned'
+
+ def test_save_existing_setting(self):
+ """Test that saving an existing setting returns the new value"""
+ # GIVEN: An existing setting
+ Settings().extend_default_settings({'test/existing value': None})
+ Settings().setValue('test/existing value', 'old value')
# WHEN a new value is saved into config
- Settings().setValue('test/extend', 'very short')
+ Settings().setValue('test/existing value', 'new value')
# THEN the new value is returned when re-read
- self.assertEqual('very short', Settings().value('test/extend'), 'The saved value should be returned')
+ assert Settings().value('test/existing value') == 'new value', 'The saved value should be returned'
def test_settings_override_with_group(self):
- """
- Test the Settings creation and its override usage - with groups
- """
+ """Test the Settings creation and its override usage - with groups"""
# GIVEN: an override for the settings
screen_settings = {
'test/extend': 'very wide',
@@ -112,9 +148,7 @@ class TestSettings(TestCase, TestMixin):
self.assertEqual('very short', Settings().value('test/extend'), 'The saved value should be returned')
def test_settings_nonexisting(self):
- """
- Test the Settings on query for non-existing value
- """
+ """Test the Settings on query for non-existing value"""
# GIVEN: A new Settings setup
with self.assertRaises(KeyError) as cm:
# WHEN reading a setting that doesn't exists
@@ -124,9 +158,7 @@ class TestSettings(TestCase, TestMixin):
self.assertEqual(str(cm.exception), "'core/does not exists'", 'We should get an exception')
def test_extend_default_settings(self):
- """
- Test that the extend_default_settings method extends the default settings
- """
+ """Test that the extend_default_settings method extends the default settings"""
# GIVEN: A patched __default_settings__ dictionary
with patch.dict(Settings.__default_settings__,
{'test/setting 1': 1, 'test/setting 2': 2, 'test/setting 3': 3}, True):
@@ -138,3 +170,125 @@ class TestSettings(TestCase, TestMixin):
self.assertEqual(
Settings.__default_settings__, {'test/setting 1': 1, 'test/setting 2': 2, 'test/setting 3': 4,
'test/extended 1': 1, 'test/extended 2': 2})
+
+ @patch('openlp.core.common.settings.QtCore.QSettings.contains')
+ @patch('openlp.core.common.settings.QtCore.QSettings.value')
+ @patch('openlp.core.common.settings.QtCore.QSettings.setValue')
+ @patch('openlp.core.common.settings.QtCore.QSettings.remove')
+ def test_upgrade_single_setting(self, mocked_remove, mocked_setValue, mocked_value, mocked_contains):
+ """Test that the upgrade mechanism for settings works correctly for single value upgrades"""
+ # GIVEN: A settings object with an upgrade step to take (99, so that we don't interfere with real ones)
+ local_settings = Settings()
+ local_settings.__setting_upgrade_99__ = [
+ ('single/value', 'single/new value', [(str, '')])
+ ]
+ settings.__version__ = 99
+ mocked_value.side_effect = [98, 10]
+ mocked_contains.return_value = True
+
+ # WHEN: upgrade_settings() is called
+ local_settings.upgrade_settings()
+
+ # THEN: The correct calls should have been made with the correct values
+ assert mocked_value.call_count == 2, 'Settings().value() should have been called twice'
+ assert mocked_value.call_args_list == [call('settings/version', 0), call('single/value')]
+ assert mocked_setValue.call_count == 2, 'Settings().setValue() should have been called twice'
+ assert mocked_setValue.call_args_list == [call('single/new value', '10'), call('settings/version', 99)]
+ mocked_contains.assert_called_once_with('single/value')
+ mocked_remove.assert_called_once_with('single/value')
+
+ @patch('openlp.core.common.settings.QtCore.QSettings.contains')
+ @patch('openlp.core.common.settings.QtCore.QSettings.value')
+ @patch('openlp.core.common.settings.QtCore.QSettings.setValue')
+ @patch('openlp.core.common.settings.QtCore.QSettings.remove')
+ def test_upgrade_setting_value(self, mocked_remove, mocked_setValue, mocked_value, mocked_contains):
+ """Test that the upgrade mechanism for settings correctly uses the new value when it's not a function"""
+ # GIVEN: A settings object with an upgrade step to take (99, so that we don't interfere with real ones)
+ local_settings = Settings()
+ local_settings.__setting_upgrade_99__ = [
+ ('values/old value', 'values/new value', [(True, 1)])
+ ]
+ settings.__version__ = 99
+ mocked_value.side_effect = [98, 1]
+ mocked_contains.return_value = True
+
+ # WHEN: upgrade_settings() is called
+ local_settings.upgrade_settings()
+
+ # THEN: The correct calls should have been made with the correct values
+ assert mocked_value.call_count == 2, 'Settings().value() should have been called twice'
+ assert mocked_value.call_args_list == [call('settings/version', 0), call('values/old value')]
+ assert mocked_setValue.call_count == 2, 'Settings().setValue() should have been called twice'
+ assert mocked_setValue.call_args_list == [call('values/new value', True), call('settings/version', 99)]
+ mocked_contains.assert_called_once_with('values/old value')
+ mocked_remove.assert_called_once_with('values/old value')
+
+ @patch('openlp.core.common.settings.QtCore.QSettings.contains')
+ @patch('openlp.core.common.settings.QtCore.QSettings.value')
+ @patch('openlp.core.common.settings.QtCore.QSettings.setValue')
+ @patch('openlp.core.common.settings.QtCore.QSettings.remove')
+ def test_upgrade_multiple_one_invalid(self, mocked_remove, mocked_setValue, mocked_value, mocked_contains):
+ """Test that the upgrade mechanism for settings works correctly for multiple values where one is invalid"""
+ # GIVEN: A settings object with an upgrade step to take
+ local_settings = Settings()
+ local_settings.__setting_upgrade_99__ = [
+ (['multiple/value 1', 'multiple/value 2'], 'single/new value', [])
+ ]
+ settings.__version__ = 99
+ mocked_value.side_effect = [98, 10]
+ mocked_contains.side_effect = [True, False]
+
+ # WHEN: upgrade_settings() is called
+ local_settings.upgrade_settings()
+
+ # THEN: The correct calls should have been made with the correct values
+ mocked_value.assert_called_once_with('settings/version', 0)
+ mocked_setValue.assert_called_once_with('settings/version', 99)
+ assert mocked_contains.call_args_list == [call('multiple/value 1'), call('multiple/value 2')]
+
+ def test_can_upgrade(self):
+ """Test the Settings.can_upgrade() method"""
+ # GIVEN: A Settings object
+ local_settings = Settings()
+
+ # WHEN: can_upgrade() is run
+ result = local_settings.can_upgrade()
+
+ # THEN: The result should be True
+ assert result is True, 'The settings should be upgradeable'
+
+ def test_convert_value_setting_none_str(self):
+ """Test the Settings._convert_value() method when a setting is None and the default value is a string"""
+ # GIVEN: A settings object
+ # WHEN: _convert_value() is run
+ result = Settings()._convert_value(None, 'string')
+
+ # THEN: The result should be an empty string
+ assert result == '', 'The result should be an empty string'
+
+ def test_convert_value_setting_none_list(self):
+ """Test the Settings._convert_value() method when a setting is None and the default value is a list"""
+ # GIVEN: A settings object
+ # WHEN: _convert_value() is run
+ result = Settings()._convert_value(None, [None])
+
+ # THEN: The result should be an empty list
+ assert result == [], 'The result should be an empty list'
+
+ def test_convert_value_setting_json_Path(self):
+ """Test the Settings._convert_value() method when a setting is JSON and represents a Path object"""
+ # GIVEN: A settings object
+ # WHEN: _convert_value() is run
+ result = Settings()._convert_value('{"__Path__": ["openlp", "core"]}', None)
+
+ # THEN: The result should be a Path object
+ assert isinstance(result, Path), 'The result should be a Path object'
+
+ def test_convert_value_setting_bool_str(self):
+ """Test the Settings._convert_value() method when a setting is supposed to be a boolean"""
+ # GIVEN: A settings object
+ # WHEN: _convert_value() is run
+ result = Settings()._convert_value('false', True)
+
+ # THEN: The result should be False
+ assert result is False, 'The result should be False'
diff --git a/tests/functional/openlp_core/lib/test_lib.py b/tests/functional/openlp_core/lib/test_lib.py
index 26c5147d5..3a87712eb 100644
--- a/tests/functional/openlp_core/lib/test_lib.py
+++ b/tests/functional/openlp_core/lib/test_lib.py
@@ -167,7 +167,7 @@ class TestLib(TestCase):
patch.object(Path, 'open'):
file_path = Path('testfile.txt')
file_path.is_file.return_value = True
- file_path.open.side_effect = IOError()
+ file_path.open.side_effect = OSError()
# WHEN: get_text_file_string is called
result = get_text_file_string(file_path)
@@ -273,32 +273,32 @@ class TestLib(TestCase):
Test the create_thumb() function with a given size.
"""
# GIVEN: An image to create a thumb of.
- image_path = os.path.join(TEST_PATH, 'church.jpg')
- thumb_path = os.path.join(TEST_PATH, 'church_thumb.jpg')
+ image_path = Path(TEST_PATH, 'church.jpg')
+ thumb_path = Path(TEST_PATH, 'church_thumb.jpg')
thumb_size = QtCore.QSize(10, 20)
# Remove the thumb so that the test actually tests if the thumb will be created. Maybe it was not deleted in the
# last test.
try:
- os.remove(thumb_path)
+ thumb_path.unlink()
except:
pass
# Only continue when the thumb does not exist.
- self.assertFalse(os.path.exists(thumb_path), 'Test was not run, because the thumb already exists.')
+ self.assertFalse(thumb_path.exists(), 'Test was not run, because the thumb already exists.')
# WHEN: Create the thumb.
icon = create_thumb(image_path, thumb_path, size=thumb_size)
# THEN: Check if the thumb was created and scaled to the given size.
- self.assertTrue(os.path.exists(thumb_path), 'Test was not ran, because the thumb already exists')
+ self.assertTrue(thumb_path.exists(), 'Test was not ran, because the thumb already exists')
self.assertIsInstance(icon, QtGui.QIcon, 'The icon should be a QIcon')
self.assertFalse(icon.isNull(), 'The icon should not be null')
- self.assertEqual(thumb_size, QtGui.QImageReader(thumb_path).size(), 'The thumb should have the given size')
+ self.assertEqual(thumb_size, QtGui.QImageReader(str(thumb_path)).size(), 'The thumb should have the given size')
# Remove the thumb so that the test actually tests if the thumb will be created.
try:
- os.remove(thumb_path)
+ thumb_path.unlink()
except:
pass
@@ -307,32 +307,33 @@ class TestLib(TestCase):
Test the create_thumb() function with no size specified.
"""
# GIVEN: An image to create a thumb of.
- image_path = os.path.join(TEST_PATH, 'church.jpg')
- thumb_path = os.path.join(TEST_PATH, 'church_thumb.jpg')
+ image_path = Path(TEST_PATH, 'church.jpg')
+ thumb_path = Path(TEST_PATH, 'church_thumb.jpg')
expected_size = QtCore.QSize(63, 88)
# Remove the thumb so that the test actually tests if the thumb will be created. Maybe it was not deleted in the
# last test.
try:
- os.remove(thumb_path)
+ thumb_path.unlink()
except:
pass
# Only continue when the thumb does not exist.
- self.assertFalse(os.path.exists(thumb_path), 'Test was not run, because the thumb already exists.')
+ self.assertFalse(thumb_path.exists(), 'Test was not run, because the thumb already exists.')
# WHEN: Create the thumb.
icon = create_thumb(image_path, thumb_path)
# THEN: Check if the thumb was created, retaining its aspect ratio.
- self.assertTrue(os.path.exists(thumb_path), 'Test was not ran, because the thumb already exists')
+ self.assertTrue(thumb_path.exists(), 'Test was not ran, because the thumb already exists')
self.assertIsInstance(icon, QtGui.QIcon, 'The icon should be a QIcon')
self.assertFalse(icon.isNull(), 'The icon should not be null')
- self.assertEqual(expected_size, QtGui.QImageReader(thumb_path).size(), 'The thumb should have the given size')
+ self.assertEqual(expected_size, QtGui.QImageReader(str(thumb_path)).size(),
+ 'The thumb should have the given size')
# Remove the thumb so that the test actually tests if the thumb will be created.
try:
- os.remove(thumb_path)
+ thumb_path.unlink()
except:
pass
@@ -341,33 +342,34 @@ class TestLib(TestCase):
Test the create_thumb() function with invalid size specified.
"""
# GIVEN: An image to create a thumb of.
- image_path = os.path.join(TEST_PATH, 'church.jpg')
- thumb_path = os.path.join(TEST_PATH, 'church_thumb.jpg')
+ image_path = Path(TEST_PATH, 'church.jpg')
+ thumb_path = Path(TEST_PATH, 'church_thumb.jpg')
thumb_size = QtCore.QSize(-1, -1)
expected_size = QtCore.QSize(63, 88)
# Remove the thumb so that the test actually tests if the thumb will be created. Maybe it was not deleted in the
# last test.
try:
- os.remove(thumb_path)
+ thumb_path.unlink()
except:
pass
# Only continue when the thumb does not exist.
- self.assertFalse(os.path.exists(thumb_path), 'Test was not run, because the thumb already exists.')
+ self.assertFalse(thumb_path.exists(), 'Test was not run, because the thumb already exists.')
# WHEN: Create the thumb.
icon = create_thumb(image_path, thumb_path, size=thumb_size)
# THEN: Check if the thumb was created, retaining its aspect ratio.
- self.assertTrue(os.path.exists(thumb_path), 'Test was not ran, because the thumb already exists')
+ self.assertTrue(thumb_path.exists(), 'Test was not ran, because the thumb already exists')
self.assertIsInstance(icon, QtGui.QIcon, 'The icon should be a QIcon')
self.assertFalse(icon.isNull(), 'The icon should not be null')
- self.assertEqual(expected_size, QtGui.QImageReader(thumb_path).size(), 'The thumb should have the given size')
+ self.assertEqual(expected_size, QtGui.QImageReader(str(thumb_path)).size(),
+ 'The thumb should have the given size')
# Remove the thumb so that the test actually tests if the thumb will be created.
try:
- os.remove(thumb_path)
+ thumb_path.unlink()
except:
pass
@@ -376,33 +378,34 @@ class TestLib(TestCase):
Test the create_thumb() function with a size of only width specified.
"""
# GIVEN: An image to create a thumb of.
- image_path = os.path.join(TEST_PATH, 'church.jpg')
- thumb_path = os.path.join(TEST_PATH, 'church_thumb.jpg')
+ image_path = Path(TEST_PATH, 'church.jpg')
+ thumb_path = Path(TEST_PATH, 'church_thumb.jpg')
thumb_size = QtCore.QSize(100, -1)
expected_size = QtCore.QSize(100, 137)
# Remove the thumb so that the test actually tests if the thumb will be created. Maybe it was not deleted in the
# last test.
try:
- os.remove(thumb_path)
+ thumb_path.unlink()
except:
pass
# Only continue when the thumb does not exist.
- self.assertFalse(os.path.exists(thumb_path), 'Test was not run, because the thumb already exists.')
+ self.assertFalse(thumb_path.exists(), 'Test was not run, because the thumb already exists.')
# WHEN: Create the thumb.
icon = create_thumb(image_path, thumb_path, size=thumb_size)
# THEN: Check if the thumb was created, retaining its aspect ratio.
- self.assertTrue(os.path.exists(thumb_path), 'Test was not ran, because the thumb already exists')
+ self.assertTrue(thumb_path.exists(), 'Test was not ran, because the thumb already exists')
self.assertIsInstance(icon, QtGui.QIcon, 'The icon should be a QIcon')
self.assertFalse(icon.isNull(), 'The icon should not be null')
- self.assertEqual(expected_size, QtGui.QImageReader(thumb_path).size(), 'The thumb should have the given size')
+ self.assertEqual(
+ expected_size, QtGui.QImageReader(str(thumb_path)).size(), 'The thumb should have the given size')
# Remove the thumb so that the test actually tests if the thumb will be created.
try:
- os.remove(thumb_path)
+ thumb_path.unlink()
except:
pass
@@ -411,33 +414,34 @@ class TestLib(TestCase):
Test the create_thumb() function with a size of only height specified.
"""
# GIVEN: An image to create a thumb of.
- image_path = os.path.join(TEST_PATH, 'church.jpg')
- thumb_path = os.path.join(TEST_PATH, 'church_thumb.jpg')
+ image_path = Path(TEST_PATH, 'church.jpg')
+ thumb_path = Path(TEST_PATH, 'church_thumb.jpg')
thumb_size = QtCore.QSize(-1, 100)
expected_size = QtCore.QSize(72, 100)
# Remove the thumb so that the test actually tests if the thumb will be created. Maybe it was not deleted in the
# last test.
try:
- os.remove(thumb_path)
+ thumb_path.unlink()
except:
pass
# Only continue when the thumb does not exist.
- self.assertFalse(os.path.exists(thumb_path), 'Test was not run, because the thumb already exists.')
+ self.assertFalse(thumb_path.exists(), 'Test was not run, because the thumb already exists.')
# WHEN: Create the thumb.
icon = create_thumb(image_path, thumb_path, size=thumb_size)
# THEN: Check if the thumb was created, retaining its aspect ratio.
- self.assertTrue(os.path.exists(thumb_path), 'Test was not ran, because the thumb already exists')
+ self.assertTrue(thumb_path.exists(), 'Test was not ran, because the thumb already exists')
self.assertIsInstance(icon, QtGui.QIcon, 'The icon should be a QIcon')
self.assertFalse(icon.isNull(), 'The icon should not be null')
- self.assertEqual(expected_size, QtGui.QImageReader(thumb_path).size(), 'The thumb should have the given size')
+ self.assertEqual(
+ expected_size, QtGui.QImageReader(str(thumb_path)).size(), 'The thumb should have the given size')
# Remove the thumb so that the test actually tests if the thumb will be created.
try:
- os.remove(thumb_path)
+ thumb_path.unlink()
except:
pass
@@ -446,8 +450,8 @@ class TestLib(TestCase):
Test the create_thumb() function with a size of only height specified.
"""
# GIVEN: An image to create a thumb of.
- image_path = os.path.join(TEST_PATH, 'church.jpg')
- thumb_path = os.path.join(TEST_PATH, 'church_thumb.jpg')
+ image_path = Path(TEST_PATH, 'church.jpg')
+ thumb_path = Path(TEST_PATH, 'church_thumb.jpg')
thumb_size = QtCore.QSize(-1, 100)
expected_size_1 = QtCore.QSize(88, 88)
expected_size_2 = QtCore.QSize(100, 100)
@@ -455,12 +459,12 @@ class TestLib(TestCase):
# Remove the thumb so that the test actually tests if the thumb will be created. Maybe it was not deleted in the
# last test.
try:
- os.remove(thumb_path)
+ thumb_path.unlink()
except:
pass
# Only continue when the thumb does not exist.
- self.assertFalse(os.path.exists(thumb_path), 'Test was not run, because the thumb already exists.')
+ self.assertFalse(thumb_path.exists(), 'Test was not run, because the thumb already exists.')
# WHEN: Create the thumb.
with patch('openlp.core.lib.QtGui.QImageReader.size') as mocked_size:
@@ -468,10 +472,11 @@ class TestLib(TestCase):
icon = create_thumb(image_path, thumb_path, size=None)
# THEN: Check if the thumb was created with aspect ratio of 1.
- self.assertTrue(os.path.exists(thumb_path), 'Test was not ran, because the thumb already exists')
+ self.assertTrue(thumb_path.exists(), 'Test was not ran, because the thumb already exists')
self.assertIsInstance(icon, QtGui.QIcon, 'The icon should be a QIcon')
self.assertFalse(icon.isNull(), 'The icon should not be null')
- self.assertEqual(expected_size_1, QtGui.QImageReader(thumb_path).size(), 'The thumb should have the given size')
+ self.assertEqual(
+ expected_size_1, QtGui.QImageReader(str(thumb_path)).size(), 'The thumb should have the given size')
# WHEN: Create the thumb.
with patch('openlp.core.lib.QtGui.QImageReader.size') as mocked_size:
@@ -481,11 +486,12 @@ class TestLib(TestCase):
# THEN: Check if the thumb was created with aspect ratio of 1.
self.assertIsInstance(icon, QtGui.QIcon, 'The icon should be a QIcon')
self.assertFalse(icon.isNull(), 'The icon should not be null')
- self.assertEqual(expected_size_2, QtGui.QImageReader(thumb_path).size(), 'The thumb should have the given size')
+ self.assertEqual(
+ expected_size_2, QtGui.QImageReader(str(thumb_path)).size(), 'The thumb should have the given size')
# Remove the thumb so that the test actually tests if the thumb will be created.
try:
- os.remove(thumb_path)
+ thumb_path.unlink()
except:
pass
diff --git a/openlp/core/ui/projector/__init__.py b/tests/functional/openlp_core/projectors/__init__.py
similarity index 96%
rename from openlp/core/ui/projector/__init__.py
rename to tests/functional/openlp_core/projectors/__init__.py
index eb20e9232..7efaa18af 100644
--- a/openlp/core/ui/projector/__init__.py
+++ b/tests/functional/openlp_core/projectors/__init__.py
@@ -20,5 +20,5 @@
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
-The Projector driver module.
+Module-level functions for the functional test suite
"""
diff --git a/tests/functional/openlp_core/projectors/test_projector_bugfixes_01.py b/tests/functional/openlp_core/projectors/test_projector_bugfixes_01.py
new file mode 100644
index 000000000..c33220d4a
--- /dev/null
+++ b/tests/functional/openlp_core/projectors/test_projector_bugfixes_01.py
@@ -0,0 +1,134 @@
+# -*- coding: utf-8 -*-
+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
+
+###############################################################################
+# OpenLP - Open Source Lyrics Projection #
+# --------------------------------------------------------------------------- #
+# Copyright (c) 2008-2015 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 #
+###############################################################################
+"""
+Package to test the openlp.core.projectors.pjlink base package.
+"""
+from unittest import TestCase
+from unittest.mock import patch
+
+from openlp.core.projectors.db import Projector
+from openlp.core.projectors.pjlink import PJLink
+
+from tests.resources.projector.data import TEST_PIN, TEST_CONNECT_AUTHENTICATE, TEST_HASH, TEST1_DATA
+
+
+class TestPJLinkBugs(TestCase):
+ """
+ Tests for the PJLink module bugfixes
+ """
+ def setUp(self):
+ '''
+ Initialization
+ '''
+ self.pjlink_test = PJLink(Projector(**TEST1_DATA), no_poll=True)
+
+ def tearDown(self):
+ '''
+ Cleanups
+ '''
+ self.pjlink_test = None
+
+ def test_bug_1550891_process_clss_nonstandard_reply_1(self):
+ """
+ Bugfix 1550891: CLSS request returns non-standard reply with Optoma/Viewsonic projector
+ """
+ # GIVEN: Test object
+ pjlink = self.pjlink_test
+
+ # WHEN: Process non-standard reply
+ pjlink.process_clss('Class 1')
+
+ # THEN: Projector class should be set with proper value
+ self.assertEqual(pjlink.pjlink_class, '1',
+ 'Non-standard class reply should have set class=1')
+
+ def test_bug_1550891_process_clss_nonstandard_reply_2(self):
+ """
+ Bugfix 1550891: CLSS request returns non-standard reply with BenQ projector
+ """
+ # GIVEN: Test object
+ pjlink = self.pjlink_test
+
+ # WHEN: Process non-standard reply
+ pjlink.process_clss('Version2')
+
+ # THEN: Projector class should be set with proper value
+ # NOTE: At this time BenQ is Class 1, but we're trying a different value to verify
+ self.assertEqual(pjlink.pjlink_class, '2',
+ 'Non-standard class reply should have set class=2')
+
+ def test_bug_1593882_no_pin_authenticated_connection(self):
+ """
+ Test bug 1593882 no pin and authenticated request exception
+ """
+ # GIVEN: Test object and mocks
+ mock_socket_timer = patch.object(self.pjlink_test, 'socket_timer').start()
+ mock_timer = patch.object(self.pjlink_test, 'timer').start()
+ mock_authentication = patch.object(self.pjlink_test, 'projectorAuthentication').start()
+ mock_ready_read = patch.object(self.pjlink_test, 'waitForReadyRead').start()
+ mock_send_command = patch.object(self.pjlink_test, 'send_command').start()
+ pjlink = self.pjlink_test
+ pjlink.pin = None
+ mock_ready_read.return_value = True
+
+ # WHEN: call with authentication request and pin not set
+ pjlink.check_login(data=TEST_CONNECT_AUTHENTICATE)
+
+ # THEN: 'No Authentication' signal should have been sent
+ mock_authentication.emit.assert_called_with(pjlink.ip)
+
+ def test_bug_1593883_pjlink_authentication(self):
+ """
+ Test bugfix 1593883 pjlink authentication
+ """
+ # GIVEN: Test object and data
+ mock_socket_timer = patch.object(self.pjlink_test, 'socket_timer').start()
+ mock_timer = patch.object(self.pjlink_test, 'timer').start()
+ mock_send_command = patch.object(self.pjlink_test, 'write').start()
+ mock_state = patch.object(self.pjlink_test, 'state').start()
+ mock_waitForReadyRead = patch.object(self.pjlink_test, 'waitForReadyRead').start()
+ pjlink = self.pjlink_test
+ pjlink.pin = TEST_PIN
+ mock_state.return_value = pjlink.ConnectedState
+ mock_waitForReadyRead.return_value = True
+
+ # WHEN: Athenticated connection is called
+ pjlink.check_login(data=TEST_CONNECT_AUTHENTICATE)
+
+ # THEN: send_command should have the proper authentication
+ self.assertEqual("{test}".format(test=mock_send_command.call_args),
+ "call(b'{hash}%1CLSS ?\\r')".format(hash=TEST_HASH))
+
+ def test_bug_1734275_process_lamp_nonstandard_reply(self):
+ """
+ Test bugfix 17342785 non-standard LAMP response
+ """
+ # GIVEN: Test object
+ pjlink = self.pjlink_test
+
+ # WHEN: Process lamp command called with only hours and no lamp power state
+ pjlink.process_lamp("45")
+
+ # THEN: Lamp should show hours as 45 and lamp power as Unavailable
+ self.assertEqual(len(pjlink.lamp), 1, 'There should only be 1 lamp available')
+ self.assertEqual(pjlink.lamp[0]['Hours'], 45, 'Lamp hours should have equalled 45')
+ self.assertIsNone(pjlink.lamp[0]['On'], 'Lamp power should be "None"')
diff --git a/tests/functional/openlp_core/lib/test_projector_constants.py b/tests/functional/openlp_core/projectors/test_projector_constants.py
similarity index 93%
rename from tests/functional/openlp_core/lib/test_projector_constants.py
rename to tests/functional/openlp_core/projectors/test_projector_constants.py
index 90fee1e13..ed1afa677 100644
--- a/tests/functional/openlp_core/lib/test_projector_constants.py
+++ b/tests/functional/openlp_core/projectors/test_projector_constants.py
@@ -20,7 +20,7 @@
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
-Package to test the openlp.core.lib.projector.constants package.
+Package to test the openlp.core.projectors.constants module.
"""
from unittest import TestCase
@@ -37,7 +37,7 @@ class TestProjectorConstants(TestCase):
from tests.resources.projector.data import TEST_VIDEO_CODES
# WHEN: Import projector PJLINK_DEFAULT_CODES
- from openlp.core.lib.projector.constants import PJLINK_DEFAULT_CODES
+ from openlp.core.projectors.constants import PJLINK_DEFAULT_CODES
# THEN: Verify dictionary was build correctly
self.assertEqual(PJLINK_DEFAULT_CODES, TEST_VIDEO_CODES, 'PJLink video strings should match')
diff --git a/tests/functional/openlp_core/lib/test_projector_db.py b/tests/functional/openlp_core/projectors/test_projector_db.py
similarity index 98%
rename from tests/functional/openlp_core/lib/test_projector_db.py
rename to tests/functional/openlp_core/projectors/test_projector_db.py
index bcbc9c547..1dfe47a54 100644
--- a/tests/functional/openlp_core/lib/test_projector_db.py
+++ b/tests/functional/openlp_core/projectors/test_projector_db.py
@@ -20,7 +20,7 @@
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
-Package to test the openlp.core.ui.projectordb find, edit, delete
+Package to test the openlp.core.projectors.db module.
record functions.
PREREQUISITE: add_record() and get_all() functions validated.
@@ -32,10 +32,10 @@ from tempfile import mkdtemp
from unittest import TestCase
from unittest.mock import patch
-from openlp.core.lib.projector import upgrade
from openlp.core.lib.db import upgrade_db
-from openlp.core.lib.projector.constants import PJLINK_PORT
-from openlp.core.lib.projector.db import Manufacturer, Model, Projector, ProjectorDB, ProjectorSource, Source
+from openlp.core.projectors import upgrade
+from openlp.core.projectors.constants import PJLINK_PORT
+from openlp.core.projectors.db import Manufacturer, Model, Projector, ProjectorDB, ProjectorSource, Source
from tests.resources.projector.data import TEST_DB_PJLINK1, TEST_DB, TEST1_DATA, TEST2_DATA, TEST3_DATA
from tests.utils.constants import TEST_RESOURCES_PATH
@@ -129,7 +129,7 @@ class TestProjectorDB(TestCase):
"""
Test case for ProjectorDB
"""
- @patch('openlp.core.lib.projector.db.init_url')
+ @patch('openlp.core.projectors.db.init_url')
def setUp(self, mocked_init_url):
"""
Set up anything necessary for all tests
diff --git a/tests/functional/openlp_core/lib/test_projector_pjlink_base.py b/tests/functional/openlp_core/projectors/test_projector_pjlink_base.py
similarity index 71%
rename from tests/functional/openlp_core/lib/test_projector_pjlink_base.py
rename to tests/functional/openlp_core/projectors/test_projector_pjlink_base.py
index 578f37ede..7253df032 100644
--- a/tests/functional/openlp_core/lib/test_projector_pjlink_base.py
+++ b/tests/functional/openlp_core/projectors/test_projector_pjlink_base.py
@@ -20,16 +20,16 @@
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
-Package to test the openlp.core.lib.projector.pjlink base package.
+Package to test the openlp.core.projectors.pjlink base package.
"""
from unittest import TestCase
from unittest.mock import call, patch, MagicMock
-from openlp.core.lib.projector.db import Projector
-from openlp.core.lib.projector.pjlink import PJLink
-from openlp.core.lib.projector.constants import E_PARAMETER, ERROR_STRING, S_ON, S_CONNECTED
+from openlp.core.projectors.constants import E_PARAMETER, ERROR_STRING, S_ON, S_CONNECTED
+from openlp.core.projectors.db import Projector
+from openlp.core.projectors.pjlink import PJLink
-from tests.resources.projector.data import TEST_PIN, TEST_SALT, TEST_CONNECT_AUTHENTICATE, TEST_HASH, TEST1_DATA
+from tests.resources.projector.data import TEST_PIN, TEST_SALT, TEST_CONNECT_AUTHENTICATE, TEST1_DATA
pjlink_test = PJLink(Projector(**TEST1_DATA), no_poll=True)
@@ -79,58 +79,6 @@ class TestPJLinkBase(TestCase):
'change_status should have been called with "{}"'.format(
ERROR_STRING[E_PARAMETER]))
- @patch.object(pjlink_test, 'send_command')
- @patch.object(pjlink_test, 'waitForReadyRead')
- @patch.object(pjlink_test, 'projectorAuthentication')
- @patch.object(pjlink_test, 'timer')
- @patch.object(pjlink_test, 'socket_timer')
- def test_bug_1593882_no_pin_authenticated_connection(self,
- mock_socket_timer,
- mock_timer,
- mock_authentication,
- mock_ready_read,
- mock_send_command):
- """
- Test bug 1593882 no pin and authenticated request exception
- """
- # GIVEN: Test object and mocks
- pjlink = pjlink_test
- pjlink.pin = None
- mock_ready_read.return_value = True
-
- # WHEN: call with authentication request and pin not set
- pjlink.check_login(data=TEST_CONNECT_AUTHENTICATE)
-
- # THEN: 'No Authentication' signal should have been sent
- mock_authentication.emit.assert_called_with(pjlink.ip)
-
- @patch.object(pjlink_test, 'waitForReadyRead')
- @patch.object(pjlink_test, 'state')
- @patch.object(pjlink_test, '_send_command')
- @patch.object(pjlink_test, 'timer')
- @patch.object(pjlink_test, 'socket_timer')
- def test_bug_1593883_pjlink_authentication(self,
- mock_socket_timer,
- mock_timer,
- mock_send_command,
- mock_state,
- mock_waitForReadyRead):
- """
- Test bugfix 1593883 pjlink authentication
- """
- # GIVEN: Test object and data
- pjlink = pjlink_test
- pjlink.pin = TEST_PIN
- mock_state.return_value = pjlink.ConnectedState
- mock_waitForReadyRead.return_value = True
-
- # WHEN: Athenticated connection is called
- pjlink.check_login(data=TEST_CONNECT_AUTHENTICATE)
-
- # THEN: send_command should have the proper authentication
- self.assertEqual("{test}".format(test=mock_send_command.call_args),
- "call(data='{hash}%1CLSS ?\\r')".format(hash=TEST_HASH))
-
@patch.object(pjlink_test, 'disconnect_from_host')
def test_socket_abort(self, mock_disconnect):
"""
diff --git a/tests/functional/openlp_core/lib/test_projector_pjlink_cmd_routing.py b/tests/functional/openlp_core/projectors/test_projector_pjlink_cmd_routing.py
similarity index 90%
rename from tests/functional/openlp_core/lib/test_projector_pjlink_cmd_routing.py
rename to tests/functional/openlp_core/projectors/test_projector_pjlink_cmd_routing.py
index 006abfff6..431da0606 100644
--- a/tests/functional/openlp_core/lib/test_projector_pjlink_cmd_routing.py
+++ b/tests/functional/openlp_core/projectors/test_projector_pjlink_cmd_routing.py
@@ -20,20 +20,20 @@
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
-Package to test the openlp.core.lib.projector.pjlink class command routing.
+Package to test the openlp.core.projectors.pjlink command routing.
"""
from unittest import TestCase
from unittest.mock import patch, MagicMock
-import openlp.core.lib.projector.pjlink
-from openlp.core.lib.projector.db import Projector
-from openlp.core.lib.projector.pjlink import PJLink
-from openlp.core.lib.projector.constants import PJLINK_ERRORS, \
+import openlp.core.projectors.pjlink
+from openlp.core.projectors.constants import PJLINK_ERRORS, \
E_AUTHENTICATION, E_PARAMETER, E_PROJECTOR, E_UNAVAILABLE, E_UNDEFINED
+from openlp.core.projectors.db import Projector
+from openlp.core.projectors.pjlink import PJLink
'''
-from openlp.core.lib.projector.constants import ERROR_STRING, PJLINK_ERST_DATA, PJLINK_ERST_STATUS, \
+from openlp.core.projectors.constants import ERROR_STRING, PJLINK_ERST_DATA, PJLINK_ERST_STATUS, \
PJLINK_POWR_STATUS, PJLINK_VALID_CMD, E_WARN, E_ERROR, S_OFF, S_STANDBY, S_ON
'''
from tests.resources.projector.data import TEST_PIN, TEST1_DATA
@@ -46,7 +46,7 @@ class TestPJLinkRouting(TestCase):
"""
Tests for the PJLink module command routing
"""
- @patch.object(openlp.core.lib.projector.pjlink, 'log')
+ @patch.object(openlp.core.projectors.pjlink, 'log')
def test_process_command_call_clss(self, mock_log):
"""
Test process_command calls proper function
@@ -66,7 +66,7 @@ class TestPJLinkRouting(TestCase):
mock_process_clss.assert_called_with('1')
@patch.object(pjlink_test, 'change_status')
- @patch.object(openlp.core.lib.projector.pjlink, 'log')
+ @patch.object(openlp.core.projectors.pjlink, 'log')
def test_process_command_err1(self, mock_log, mock_change_status):
"""
Test ERR1 - Undefined projector function
@@ -85,7 +85,7 @@ class TestPJLinkRouting(TestCase):
mock_log.error.assert_called_with(log_text)
@patch.object(pjlink_test, 'change_status')
- @patch.object(openlp.core.lib.projector.pjlink, 'log')
+ @patch.object(openlp.core.projectors.pjlink, 'log')
def test_process_command_err2(self, mock_log, mock_change_status):
"""
Test ERR2 - Parameter Error
@@ -104,7 +104,7 @@ class TestPJLinkRouting(TestCase):
mock_log.error.assert_called_with(log_text)
@patch.object(pjlink_test, 'change_status')
- @patch.object(openlp.core.lib.projector.pjlink, 'log')
+ @patch.object(openlp.core.projectors.pjlink, 'log')
def test_process_command_err3(self, mock_log, mock_change_status):
"""
Test ERR3 - Unavailable error
@@ -123,7 +123,7 @@ class TestPJLinkRouting(TestCase):
mock_log.error.assert_called_with(log_text)
@patch.object(pjlink_test, 'change_status')
- @patch.object(openlp.core.lib.projector.pjlink, 'log')
+ @patch.object(openlp.core.projectors.pjlink, 'log')
def test_process_command_err4(self, mock_log, mock_change_status):
"""
Test ERR3 - Unavailable error
@@ -144,7 +144,7 @@ class TestPJLinkRouting(TestCase):
@patch.object(pjlink_test, 'projectorAuthentication')
@patch.object(pjlink_test, 'change_status')
@patch.object(pjlink_test, 'disconnect_from_host')
- @patch.object(openlp.core.lib.projector.pjlink, 'log')
+ @patch.object(openlp.core.projectors.pjlink, 'log')
def test_process_command_erra(self, mock_log, mock_disconnect, mock_change_status, mock_err_authenticate):
"""
Test ERRA - Authentication Error
@@ -163,7 +163,7 @@ class TestPJLinkRouting(TestCase):
mock_change_status.assert_called_once_with(E_AUTHENTICATION)
mock_log.error.assert_called_with(log_text)
- @patch.object(openlp.core.lib.projector.pjlink, 'log')
+ @patch.object(openlp.core.projectors.pjlink, 'log')
def test_process_command_future(self, mock_log):
"""
Test command valid but no method to process yet
@@ -184,7 +184,7 @@ class TestPJLinkRouting(TestCase):
mock_log.warning.assert_called_once_with(log_text)
@patch.object(pjlink_test, 'pjlink_functions')
- @patch.object(openlp.core.lib.projector.pjlink, 'log')
+ @patch.object(openlp.core.projectors.pjlink, 'log')
def test_process_command_invalid(self, mock_log, mock_functions):
"""
Test not a valid command
@@ -203,7 +203,7 @@ class TestPJLinkRouting(TestCase):
mock_log.error.assert_called_once_with(log_text)
@patch.object(pjlink_test, 'pjlink_functions')
- @patch.object(openlp.core.lib.projector.pjlink, 'log')
+ @patch.object(openlp.core.projectors.pjlink, 'log')
def test_process_command_ok(self, mock_log, mock_functions):
"""
Test command returned success
diff --git a/tests/functional/openlp_core/lib/test_projector_pjlink_commands.py b/tests/functional/openlp_core/projectors/test_projector_pjlink_commands.py
similarity index 94%
rename from tests/functional/openlp_core/lib/test_projector_pjlink_commands.py
rename to tests/functional/openlp_core/projectors/test_projector_pjlink_commands.py
index 143206d0a..32544dd09 100644
--- a/tests/functional/openlp_core/lib/test_projector_pjlink_commands.py
+++ b/tests/functional/openlp_core/projectors/test_projector_pjlink_commands.py
@@ -20,18 +20,18 @@
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
-Package to test the openlp.core.lib.projector.pjlink commands package.
+Package to test the openlp.core.projectors.pjlink commands package.
"""
from unittest import TestCase
from unittest.mock import patch
-import openlp.core.lib.projector.pjlink
-from openlp.core.lib.projector.db import Projector
-from openlp.core.lib.projector.pjlink import PJLink
-from openlp.core.lib.projector.constants import ERROR_STRING, PJLINK_ERST_DATA, PJLINK_ERST_STATUS, \
+import openlp.core.projectors.pjlink
+from openlp.core.projectors.constants import ERROR_STRING, PJLINK_ERST_DATA, PJLINK_ERST_STATUS, \
PJLINK_POWR_STATUS, \
E_ERROR, E_NOT_CONNECTED, E_SOCKET_ADDRESS_NOT_AVAILABLE, E_UNKNOWN_SOCKET_ERROR, E_WARN, \
S_CONNECTED, S_OFF, S_ON, S_NOT_CONNECTED, S_CONNECTING, S_STANDBY
+from openlp.core.projectors.db import Projector
+from openlp.core.projectors.pjlink import PJLink
from tests.resources.projector.data import TEST_PIN, TEST1_DATA
@@ -50,7 +50,7 @@ class TestPJLinkCommands(TestCase):
Tests for the PJLink module
"""
@patch.object(pjlink_test, 'changeStatus')
- @patch.object(openlp.core.lib.projector.pjlink, 'log')
+ @patch.object(openlp.core.projectors.pjlink, 'log')
def test_projector_change_status_connection_error(self, mock_log, mock_change_status):
"""
Test change_status with connection error
@@ -74,7 +74,7 @@ class TestPJLinkCommands(TestCase):
self.assertEqual(mock_log.debug.call_count, 3, 'Debug log should have been called 3 times')
@patch.object(pjlink_test, 'changeStatus')
- @patch.object(openlp.core.lib.projector.pjlink, 'log')
+ @patch.object(openlp.core.projectors.pjlink, 'log')
def test_projector_change_status_connection_status_connecting(self, mock_log, mock_change_status):
"""
Test change_status with connection status
@@ -97,7 +97,7 @@ class TestPJLinkCommands(TestCase):
self.assertEqual(mock_log.debug.call_count, 3, 'Debug log should have been called 3 times')
@patch.object(pjlink_test, 'changeStatus')
- @patch.object(openlp.core.lib.projector.pjlink, 'log')
+ @patch.object(openlp.core.projectors.pjlink, 'log')
def test_projector_change_status_connection_status_connected(self, mock_log, mock_change_status):
"""
Test change_status with connection status
@@ -120,7 +120,7 @@ class TestPJLinkCommands(TestCase):
self.assertEqual(mock_log.debug.call_count, 3, 'Debug log should have been called 3 times')
@patch.object(pjlink_test, 'changeStatus')
- @patch.object(openlp.core.lib.projector.pjlink, 'log')
+ @patch.object(openlp.core.projectors.pjlink, 'log')
def test_projector_change_status_connection_status_with_message(self, mock_log, mock_change_status):
"""
Test change_status with connection status
@@ -144,7 +144,7 @@ class TestPJLinkCommands(TestCase):
self.assertEqual(mock_log.debug.call_count, 3, 'Debug log should have been called 3 times')
@patch.object(pjlink_test, 'send_command')
- @patch.object(openlp.core.lib.projector.pjlink, 'log')
+ @patch.object(openlp.core.projectors.pjlink, 'log')
def test_projector_get_av_mute_status(self, mock_log, mock_send_command):
"""
Test sending command to retrieve shutter/audio state
@@ -164,7 +164,7 @@ class TestPJLinkCommands(TestCase):
mock_send_command.assert_called_once_with(cmd=test_data)
@patch.object(pjlink_test, 'send_command')
- @patch.object(openlp.core.lib.projector.pjlink, 'log')
+ @patch.object(openlp.core.projectors.pjlink, 'log')
def test_projector_get_available_inputs(self, mock_log, mock_send_command):
"""
Test sending command to retrieve avaliable inputs
@@ -184,7 +184,7 @@ class TestPJLinkCommands(TestCase):
mock_send_command.assert_called_once_with(cmd=test_data)
@patch.object(pjlink_test, 'send_command')
- @patch.object(openlp.core.lib.projector.pjlink, 'log')
+ @patch.object(openlp.core.projectors.pjlink, 'log')
def test_projector_get_error_status(self, mock_log, mock_send_command):
"""
Test sending command to retrieve projector error status
@@ -204,7 +204,7 @@ class TestPJLinkCommands(TestCase):
mock_send_command.assert_called_once_with(cmd=test_data)
@patch.object(pjlink_test, 'send_command')
- @patch.object(openlp.core.lib.projector.pjlink, 'log')
+ @patch.object(openlp.core.projectors.pjlink, 'log')
def test_projector_get_input_source(self, mock_log, mock_send_command):
"""
Test sending command to retrieve current input
@@ -224,7 +224,7 @@ class TestPJLinkCommands(TestCase):
mock_send_command.assert_called_once_with(cmd=test_data)
@patch.object(pjlink_test, 'send_command')
- @patch.object(openlp.core.lib.projector.pjlink, 'log')
+ @patch.object(openlp.core.projectors.pjlink, 'log')
def test_projector_get_lamp_status(self, mock_log, mock_send_command):
"""
Test sending command to retrieve lamp(s) status
@@ -244,7 +244,7 @@ class TestPJLinkCommands(TestCase):
mock_send_command.assert_called_once_with(cmd=test_data)
@patch.object(pjlink_test, 'send_command')
- @patch.object(openlp.core.lib.projector.pjlink, 'log')
+ @patch.object(openlp.core.projectors.pjlink, 'log')
def test_projector_get_manufacturer(self, mock_log, mock_send_command):
"""
Test sending command to retrieve manufacturer name
@@ -264,7 +264,7 @@ class TestPJLinkCommands(TestCase):
mock_send_command.assert_called_once_with(cmd=test_data)
@patch.object(pjlink_test, 'send_command')
- @patch.object(openlp.core.lib.projector.pjlink, 'log')
+ @patch.object(openlp.core.projectors.pjlink, 'log')
def test_projector_get_model(self, mock_log, mock_send_command):
"""
Test sending command to get model information
@@ -284,7 +284,7 @@ class TestPJLinkCommands(TestCase):
mock_send_command.assert_called_once_with(cmd=test_data)
@patch.object(pjlink_test, 'send_command')
- @patch.object(openlp.core.lib.projector.pjlink, 'log')
+ @patch.object(openlp.core.projectors.pjlink, 'log')
def test_projector_get_name(self, mock_log, mock_send_command):
"""
Test sending command to get user-assigned name
@@ -304,7 +304,7 @@ class TestPJLinkCommands(TestCase):
mock_send_command.assert_called_once_with(cmd=test_data)
@patch.object(pjlink_test, 'send_command')
- @patch.object(openlp.core.lib.projector.pjlink, 'log')
+ @patch.object(openlp.core.projectors.pjlink, 'log')
def test_projector_get_other_info(self, mock_log, mock_send_command):
"""
Test sending command to retrieve other information
@@ -324,7 +324,7 @@ class TestPJLinkCommands(TestCase):
mock_send_command.assert_called_once_with(cmd=test_data)
@patch.object(pjlink_test, 'send_command')
- @patch.object(openlp.core.lib.projector.pjlink, 'log')
+ @patch.object(openlp.core.projectors.pjlink, 'log')
def test_projector_get_power_status(self, mock_log, mock_send_command):
"""
Test sending command to retrieve current power state
@@ -570,36 +570,7 @@ class TestPJLinkCommands(TestCase):
self.assertEqual(pjlink.pjlink_class, '2',
'Projector should have set class=2')
- def test_projector_process_clss_nonstandard_reply_optoma(self):
- """
- Bugfix 1550891: CLSS request returns non-standard reply with Optoma projector
- """
- # GIVEN: Test object
- pjlink = pjlink_test
-
- # WHEN: Process non-standard reply
- pjlink.process_clss('Class 1')
-
- # THEN: Projector class should be set with proper value
- self.assertEqual(pjlink.pjlink_class, '1',
- 'Non-standard class reply should have set class=1')
-
- def test_projector_process_clss_nonstandard_reply_benq(self):
- """
- Bugfix 1550891: CLSS request returns non-standard reply with BenQ projector
- """
- # GIVEN: Test object
- pjlink = pjlink_test
-
- # WHEN: Process non-standard reply
- pjlink.process_clss('Version2')
-
- # THEN: Projector class should be set with proper value
- # NOTE: At this time BenQ is Class 1, but we're trying a different value to verify
- self.assertEqual(pjlink.pjlink_class, '2',
- 'Non-standard class reply should have set class=2')
-
- @patch.object(openlp.core.lib.projector.pjlink, 'log')
+ @patch.object(openlp.core.projectors.pjlink, 'log')
def test_projector_process_clss_invalid_nan(self, mock_log):
"""
Test CLSS reply has no class number
@@ -616,7 +587,7 @@ class TestPJLinkCommands(TestCase):
'Non-standard class reply should have set class=1')
mock_log.error.assert_called_once_with(log_text)
- @patch.object(openlp.core.lib.projector.pjlink, 'log')
+ @patch.object(openlp.core.projectors.pjlink, 'log')
def test_projector_process_clss_invalid_no_version(self, mock_log):
"""
Test CLSS reply has no class number
@@ -648,7 +619,7 @@ class TestPJLinkCommands(TestCase):
# THEN: PJLink instance errors should be None
self.assertIsNone(pjlink.projector_errors, 'projector_errors should have been set to None')
- @patch.object(openlp.core.lib.projector.pjlink, 'log')
+ @patch.object(openlp.core.projectors.pjlink, 'log')
def test_projector_process_erst_data_invalid_length(self, mock_log):
"""
Test test_projector_process_erst_data_invalid_length
@@ -666,7 +637,7 @@ class TestPJLinkCommands(TestCase):
self.assertTrue(mock_log.warning.called, 'Warning should have been logged')
mock_log.warning.assert_called_once_with(log_text)
- @patch.object(openlp.core.lib.projector.pjlink, 'log')
+ @patch.object(openlp.core.projectors.pjlink, 'log')
def test_projector_process_erst_data_invalid_nan(self, mock_log):
"""
Test test_projector_process_erst_data_invalid_nan
@@ -764,7 +735,7 @@ class TestPJLinkCommands(TestCase):
self.assertEqual(pjlink.source, '1', 'Input source should be set to "1"')
@patch.object(pjlink_test, 'projectorUpdateIcons')
- @patch.object(openlp.core.lib.projector.pjlink, 'log')
+ @patch.object(openlp.core.projectors.pjlink, 'log')
def test_projector_process_inst(self, mock_log, mock_UpdateIcons):
"""
Test saving video source available information
@@ -787,7 +758,7 @@ class TestPJLinkCommands(TestCase):
mock_log.debug.assert_called_once_with(log_data)
self.assertTrue(mock_UpdateIcons.emit.called, 'Update Icons should have been called')
- @patch.object(openlp.core.lib.projector.pjlink, 'log')
+ @patch.object(openlp.core.projectors.pjlink, 'log')
def test_projector_process_lamp_invalid(self, mock_log):
"""
Test status multiple lamp on/off and hours
@@ -858,7 +829,7 @@ class TestPJLinkCommands(TestCase):
self.assertEqual(pjlink.lamp[0]['Hours'], 22222,
'Lamp hours should have been set to 22222')
- @patch.object(openlp.core.lib.projector.pjlink, 'log')
+ @patch.object(openlp.core.projectors.pjlink, 'log')
def test_projector_process_name(self, mock_log):
"""
Test saving NAME data from projector
@@ -1040,7 +1011,7 @@ class TestPJLinkCommands(TestCase):
self.assertNotEquals(pjlink.serial_no, test_number,
'Projector serial number should NOT have been set')
- @patch.object(openlp.core.lib.projector.pjlink, 'log')
+ @patch.object(openlp.core.projectors.pjlink, 'log')
def test_projector_process_sver(self, mock_log):
"""
Test invalid software version information - too long
@@ -1061,7 +1032,7 @@ class TestPJLinkCommands(TestCase):
self.assertIsNone(pjlink.sw_version_received, 'Received software version should not have changed')
mock_log.debug.assert_called_once_with(test_log)
- @patch.object(openlp.core.lib.projector.pjlink, 'log')
+ @patch.object(openlp.core.projectors.pjlink, 'log')
def test_projector_process_sver_changed(self, mock_log):
"""
Test invalid software version information - Received different than saved
@@ -1086,7 +1057,7 @@ class TestPJLinkCommands(TestCase):
# There was 4 calls, but only the last one is checked with this method
mock_log.warning.assert_called_with(test_log)
- @patch.object(openlp.core.lib.projector.pjlink, 'log')
+ @patch.object(openlp.core.projectors.pjlink, 'log')
def test_projector_process_sver_invalid(self, mock_log):
"""
Test invalid software version information - too long
diff --git a/tests/functional/openlp_core/ui/test_first_time.py b/tests/functional/openlp_core/ui/test_first_time.py
index eb9464375..2be5e1ad6 100644
--- a/tests/functional/openlp_core/ui/test_first_time.py
+++ b/tests/functional/openlp_core/ui/test_first_time.py
@@ -40,7 +40,7 @@ class TestFirstTimeWizard(TestMixin, TestCase):
Test get_web_page will attempt CONNECTION_RETRIES+1 connections - bug 1409031
"""
# GIVEN: Initial settings and mocks
- mocked_requests.get.side_effect = IOError('Unable to connect')
+ mocked_requests.get.side_effect = OSError('Unable to connect')
# WHEN: A webpage is requested
try:
diff --git a/tests/functional/openlp_core/ui/test_servicemanager.py b/tests/functional/openlp_core/ui/test_servicemanager.py
index adf241d35..3c0958506 100644
--- a/tests/functional/openlp_core/ui/test_servicemanager.py
+++ b/tests/functional/openlp_core/ui/test_servicemanager.py
@@ -637,7 +637,7 @@ class TestServiceManager(TestCase):
Registry().register('main_window', mocked_main_window)
Registry().register('application', MagicMock())
service_manager = ServiceManager(None)
- service_manager._file_name = os.path.join('temp', 'filename.osz')
+ service_manager._service_path = os.path.join('temp', 'filename.osz')
service_manager._save_lite = False
service_manager.service_items = []
service_manager.service_theme = 'Default'
@@ -666,7 +666,7 @@ class TestServiceManager(TestCase):
Registry().register('main_window', mocked_main_window)
Registry().register('application', MagicMock())
service_manager = ServiceManager(None)
- service_manager._file_name = os.path.join('temp', 'filename.osz')
+ service_manager._service_path = os.path.join('temp', 'filename.osz')
service_manager._save_lite = False
service_manager.service_items = []
service_manager.service_theme = 'Default'
diff --git a/tests/functional/openlp_core/ui/test_slidecontroller.py b/tests/functional/openlp_core/ui/test_slidecontroller.py
index 7bcd44cc6..c9335fd63 100644
--- a/tests/functional/openlp_core/ui/test_slidecontroller.py
+++ b/tests/functional/openlp_core/ui/test_slidecontroller.py
@@ -208,6 +208,33 @@ class TestSlideController(TestCase):
mocked_on_theme_display.assert_called_once_with(False)
mocked_on_hide_display.assert_called_once_with(False)
+ def test_on_go_live_preview_controller(self):
+ """
+ Test that when the on_go_preview() method is called the message is sent to the preview controller and focus is
+ set correctly.
+ """
+ # GIVEN: A new SlideController instance and plugin preview then pressing go live should respond
+ mocked_display = MagicMock()
+ mocked_preview_controller = MagicMock()
+ mocked_preview_widget = MagicMock()
+ mocked_service_item = MagicMock()
+ mocked_service_item.from_service = False
+ mocked_preview_widget.current_slide_number.return_value = 1
+ mocked_preview_widget.slide_count = MagicMock(return_value=2)
+ mocked_preview_controller.preview_widget = MagicMock()
+ Registry.create()
+ Registry().register('preview_controller', mocked_preview_controller)
+ slide_controller = SlideController(None)
+ slide_controller.service_item = mocked_service_item
+ slide_controller.preview_widget = mocked_preview_widget
+ slide_controller.display = mocked_display
+
+ # WHEN: on_go_live() is called
+ slide_controller.on_go_preview()
+
+ # THEN: the preview controller should have the service item and the focus set to live
+ mocked_preview_controller.preview_widget.setFocus.assert_called_once_with()
+
def test_on_go_live_live_controller(self):
"""
Test that when the on_go_live() method is called the message is sent to the live controller and focus is
diff --git a/tests/functional/openlp_core/ui/test_thememanager.py b/tests/functional/openlp_core/ui/test_thememanager.py
index 62bf980b4..f44f558b0 100644
--- a/tests/functional/openlp_core/ui/test_thememanager.py
+++ b/tests/functional/openlp_core/ui/test_thememanager.py
@@ -199,16 +199,16 @@ class TestThemeManager(TestCase):
theme_manager._create_theme_from_xml = MagicMock()
theme_manager.generate_and_save_image = MagicMock()
theme_manager.theme_path = None
- folder = Path(mkdtemp())
+ folder_path = Path(mkdtemp())
theme_file = Path(TEST_RESOURCES_PATH, 'themes', 'Moss_on_tree.otz')
# WHEN: We try to unzip it
- theme_manager.unzip_theme(theme_file, folder)
+ theme_manager.unzip_theme(theme_file, folder_path)
# THEN: Files should be unpacked
- self.assertTrue((folder / 'Moss on tree' / 'Moss on tree.xml').exists())
+ self.assertTrue((folder_path / 'Moss on tree' / 'Moss on tree.xml').exists())
self.assertEqual(mocked_critical_error_message_box.call_count, 0, 'No errors should have happened')
- shutil.rmtree(str(folder))
+ folder_path.rmtree()
def test_unzip_theme_invalid_version(self):
"""
diff --git a/tests/functional/openlp_core/widgets/test_views.py b/tests/functional/openlp_core/widgets/test_views.py
index d931a5ef5..0fa028f11 100644
--- a/tests/functional/openlp_core/widgets/test_views.py
+++ b/tests/functional/openlp_core/widgets/test_views.py
@@ -627,4 +627,3 @@ class TestTreeWidgetWithDnD(TestCase):
assert widget.allow_internal_dnd is False
assert widget.indentation() == 0
assert widget.isAnimated() is True
-
diff --git a/tests/functional/openlp_plugins/bibles/test_bibleimport.py b/tests/functional/openlp_plugins/bibles/test_bibleimport.py
index 0e8e43172..9b9a8fae8 100644
--- a/tests/functional/openlp_plugins/bibles/test_bibleimport.py
+++ b/tests/functional/openlp_plugins/bibles/test_bibleimport.py
@@ -30,6 +30,7 @@ from lxml import etree, objectify
from PyQt5.QtWidgets import QDialog
from openlp.core.common.i18n import Language
+from openlp.core.common.path import Path
from openlp.core.lib.exceptions import ValidationError
from openlp.plugins.bibles.lib.bibleimport import BibleImport
from openlp.plugins.bibles.lib.db import BibleDB
@@ -48,7 +49,7 @@ class TestBibleImport(TestCase):
b' Testdatatodiscard\n'
b''
)
- self.open_patcher = patch('builtins.open')
+ self.open_patcher = patch.object(Path, 'open')
self.addCleanup(self.open_patcher.stop)
self.mocked_open = self.open_patcher.start()
self.critical_error_message_box_patcher = \
@@ -74,8 +75,8 @@ class TestBibleImport(TestCase):
# WHEN: Creating an instance of BibleImport with no key word arguments
instance = BibleImport(MagicMock())
- # THEN: The filename attribute should be None
- assert instance.filename is None
+ # THEN: The file_path attribute should be None
+ assert instance.file_path is None
assert isinstance(instance, BibleDB)
def test_init_kwargs_set(self):
@@ -84,11 +85,11 @@ class TestBibleImport(TestCase):
"""
# GIVEN: A patched BibleDB._setup, BibleImport class and mocked parent
# WHEN: Creating an instance of BibleImport with selected key word arguments
- kwargs = {'filename': 'bible.xml'}
+ kwargs = {'file_path': 'bible.xml'}
instance = BibleImport(MagicMock(), **kwargs)
- # THEN: The filename keyword should be set to bible.xml
- assert instance.filename == 'bible.xml'
+ # THEN: The file_path keyword should be set to bible.xml
+ assert instance.file_path == 'bible.xml'
assert isinstance(instance, BibleDB)
@patch.object(BibleDB, '_setup')
@@ -361,7 +362,7 @@ class TestBibleImport(TestCase):
instance.wizard = MagicMock()
# WHEN: Calling parse_xml
- result = instance.parse_xml('file.tst')
+ result = instance.parse_xml(Path('file.tst'))
# THEN: The result returned should contain the correct data, and should be an instance of eetree_Element
assert etree.tostring(result) == b'\n \n' \
@@ -378,7 +379,7 @@ class TestBibleImport(TestCase):
instance.wizard = MagicMock()
# WHEN: Calling parse_xml
- result = instance.parse_xml('file.tst', use_objectify=True)
+ result = instance.parse_xml(Path('file.tst'), use_objectify=True)
# THEN: The result returned should contain the correct data, and should be an instance of ObjectifiedElement
assert etree.tostring(result) == b'' \
@@ -396,7 +397,7 @@ class TestBibleImport(TestCase):
instance.wizard = MagicMock()
# WHEN: Calling parse_xml, with a test file
- result = instance.parse_xml('file.tst', elements=elements)
+ result = instance.parse_xml(Path('file.tst'), elements=elements)
# THEN: The result returned should contain the correct data
assert etree.tostring(result) == \
@@ -413,7 +414,7 @@ class TestBibleImport(TestCase):
instance.wizard = MagicMock()
# WHEN: Calling parse_xml, with a test file
- result = instance.parse_xml('file.tst', tags=tags)
+ result = instance.parse_xml(Path('file.tst'), tags=tags)
# THEN: The result returned should contain the correct data
assert etree.tostring(result) == b'\n Testdatatokeep\n Test' \
@@ -431,7 +432,7 @@ class TestBibleImport(TestCase):
instance.wizard = MagicMock()
# WHEN: Calling parse_xml, with a test file
- result = instance.parse_xml('file.tst', elements=elements, tags=tags)
+ result = instance.parse_xml(Path('file.tst'), elements=elements, tags=tags)
# THEN: The result returned should contain the correct data
assert etree.tostring(result) == b'\n Testdatatokeep\n \n'
@@ -446,10 +447,10 @@ class TestBibleImport(TestCase):
exception.filename = 'file.tst'
exception.strerror = 'No such file or directory'
self.mocked_open.side_effect = exception
- importer = BibleImport(MagicMock(), path='.', name='.', filename='')
+ importer = BibleImport(MagicMock(), path='.', name='.', file_path=None)
# WHEN: Calling parse_xml
- result = importer.parse_xml('file.tst')
+ result = importer.parse_xml(Path('file.tst'))
# THEN: parse_xml should have caught the error, informed the user and returned None
mocked_log_exception.assert_called_once_with('Opening file.tst failed.')
@@ -468,10 +469,10 @@ class TestBibleImport(TestCase):
exception.filename = 'file.tst'
exception.strerror = 'Permission denied'
self.mocked_open.side_effect = exception
- importer = BibleImport(MagicMock(), path='.', name='.', filename='')
+ importer = BibleImport(MagicMock(), path='.', name='.', file_path=None)
# WHEN: Calling parse_xml
- result = importer.parse_xml('file.tst')
+ result = importer.parse_xml(Path('file.tst'))
# THEN: parse_xml should have caught the error, informed the user and returned None
mocked_log_exception.assert_called_once_with('Opening file.tst failed.')
@@ -485,7 +486,7 @@ class TestBibleImport(TestCase):
Test set_current_chapter
"""
# GIVEN: An instance of BibleImport and a mocked wizard
- importer = BibleImport(MagicMock(), path='.', name='.', filename='')
+ importer = BibleImport(MagicMock(), path='.', name='.', file_path=None)
importer.wizard = MagicMock()
# WHEN: Calling set_current_chapter
@@ -500,7 +501,7 @@ class TestBibleImport(TestCase):
Test that validate_xml_file raises a ValidationError when is_compressed returns True
"""
# GIVEN: A mocked parse_xml which returns None
- importer = BibleImport(MagicMock(), path='.', name='.', filename='')
+ importer = BibleImport(MagicMock(), path='.', name='.', file_path=None)
# WHEN: Calling is_compressed
# THEN: ValidationError should be raised, with the message 'Compressed file'
@@ -515,7 +516,7 @@ class TestBibleImport(TestCase):
Test that validate_xml_file raises a ValidationError when parse_xml returns None
"""
# GIVEN: A mocked parse_xml which returns None
- importer = BibleImport(MagicMock(), path='.', name='.', filename='')
+ importer = BibleImport(MagicMock(), path='.', name='.', file_path=None)
# WHEN: Calling validate_xml_file
# THEN: ValidationError should be raised, with the message 'Error when opening file'
@@ -531,7 +532,7 @@ class TestBibleImport(TestCase):
Test that validate_xml_file returns True with valid XML
"""
# GIVEN: Some test data with an OpenSong Bible "bible" root tag
- importer = BibleImport(MagicMock(), path='.', name='.', filename='')
+ importer = BibleImport(MagicMock(), path='.', name='.', file_path=None)
# WHEN: Calling validate_xml_file
result = importer.validate_xml_file('file.name', 'bible')
@@ -546,7 +547,7 @@ class TestBibleImport(TestCase):
Test that validate_xml_file raises a ValidationError with an OpenSong root tag
"""
# GIVEN: Some test data with an Zefania root tag and an instance of BibleImport
- importer = BibleImport(MagicMock(), path='.', name='.', filename='')
+ importer = BibleImport(MagicMock(), path='.', name='.', file_path=None)
# WHEN: Calling validate_xml_file
# THEN: ValidationError should be raised, and the critical error message box should was called informing
@@ -566,7 +567,7 @@ class TestBibleImport(TestCase):
# GIVEN: Some test data with an Zefania root tag and an instance of BibleImport
mocked_parse_xml.return_value = objectify.fromstring(
'')
- importer = BibleImport(MagicMock(), path='.', name='.', filename='')
+ importer = BibleImport(MagicMock(), path='.', name='.', file_path=None)
# WHEN: Calling validate_xml_file
# THEN: ValidationError should be raised, and the critical error message box should was called informing
@@ -584,7 +585,7 @@ class TestBibleImport(TestCase):
Test that validate_xml_file raises a ValidationError with an Zefania root tag
"""
# GIVEN: Some test data with an Zefania root tag and an instance of BibleImport
- importer = BibleImport(MagicMock(), path='.', name='.', filename='')
+ importer = BibleImport(MagicMock(), path='.', name='.', file_path=None)
# WHEN: Calling validate_xml_file
# THEN: ValidationError should be raised, and the critical error message box should was called informing
@@ -602,7 +603,7 @@ class TestBibleImport(TestCase):
Test that validate_xml_file raises a ValidationError with an unknown root tag
"""
# GIVEN: Some test data with an unknown root tag and an instance of BibleImport
- importer = BibleImport(MagicMock(), path='.', name='.', filename='')
+ importer = BibleImport(MagicMock(), path='.', name='.', file_path=None)
# WHEN: Calling validate_xml_file
# THEN: ValidationError should be raised, and the critical error message box should was called informing
diff --git a/tests/functional/openlp_plugins/bibles/test_csvimport.py b/tests/functional/openlp_plugins/bibles/test_csvimport.py
index 608e44bb9..4d948f53b 100644
--- a/tests/functional/openlp_plugins/bibles/test_csvimport.py
+++ b/tests/functional/openlp_plugins/bibles/test_csvimport.py
@@ -29,6 +29,7 @@ from collections import namedtuple
from unittest import TestCase
from unittest.mock import ANY, MagicMock, PropertyMock, call, patch
+from openlp.core.common.path import Path
from openlp.core.lib.exceptions import ValidationError
from openlp.plugins.bibles.lib.bibleimport import BibleImport
from openlp.plugins.bibles.lib.importers.csvbible import Book, CSVBible, Verse
@@ -59,12 +60,13 @@ class TestCSVImport(TestCase):
mocked_manager = MagicMock()
# WHEN: An importer object is created
- importer = CSVBible(mocked_manager, path='.', name='.', booksfile='books.csv', versefile='verse.csv')
+ importer = \
+ CSVBible(mocked_manager, path='.', name='.', books_path=Path('books.csv'), verse_path=Path('verse.csv'))
# THEN: The importer should be an instance of BibleImport
self.assertIsInstance(importer, BibleImport)
- self.assertEqual(importer.books_file, 'books.csv')
- self.assertEqual(importer.verses_file, 'verse.csv')
+ self.assertEqual(importer.books_path, Path('books.csv'))
+ self.assertEqual(importer.verses_path, Path('verse.csv'))
def test_book_namedtuple(self):
"""
@@ -134,17 +136,17 @@ class TestCSVImport(TestCase):
with patch('openlp.plugins.bibles.lib.importers.csvbible.get_file_encoding',
return_value={'encoding': 'utf-8', 'confidence': 0.99}),\
- patch('openlp.plugins.bibles.lib.importers.csvbible.open', create=True) as mocked_open,\
+ patch('openlp.plugins.bibles.lib.importers.csvbible.Path.open', create=True) as mocked_open,\
patch('openlp.plugins.bibles.lib.importers.csvbible.csv.reader',
return_value=iter(test_data)) as mocked_reader:
# WHEN: Calling the CSVBible parse_csv_file method with a file name and TestTuple
- result = CSVBible.parse_csv_file('file.csv', TestTuple)
+ result = CSVBible.parse_csv_file(Path('file.csv'), TestTuple)
# THEN: A list of TestTuple instances with the parsed data should be returned
self.assertEqual(result, [TestTuple('1', 'Line 1', 'Data 1'), TestTuple('2', 'Line 2', 'Data 2'),
TestTuple('3', 'Line 3', 'Data 3')])
- mocked_open.assert_called_once_with('file.csv', 'r', encoding='utf-8', newline='')
+ mocked_open.assert_called_once_with('r', encoding='utf-8', newline='')
mocked_reader.assert_called_once_with(ANY, delimiter=',', quotechar='"')
def test_parse_csv_file_oserror(self):
@@ -154,12 +156,12 @@ class TestCSVImport(TestCase):
# GIVEN: Mocked a mocked open object which raises an OSError
with patch('openlp.plugins.bibles.lib.importers.csvbible.get_file_encoding',
return_value={'encoding': 'utf-8', 'confidence': 0.99}),\
- patch('openlp.plugins.bibles.lib.importers.csvbible.open', side_effect=OSError, create=True):
+ patch('openlp.plugins.bibles.lib.importers.csvbible.Path.open', side_effect=OSError, create=True):
# WHEN: Calling CSVBible.parse_csv_file
# THEN: A ValidationError should be raised
with self.assertRaises(ValidationError) as context:
- CSVBible.parse_csv_file('file.csv', None)
+ CSVBible.parse_csv_file(Path('file.csv'), None)
self.assertEqual(context.exception.msg, 'Parsing "file.csv" failed')
def test_parse_csv_file_csverror(self):
@@ -169,13 +171,13 @@ class TestCSVImport(TestCase):
# GIVEN: Mocked a csv.reader which raises an csv.Error
with patch('openlp.plugins.bibles.lib.importers.csvbible.get_file_encoding',
return_value={'encoding': 'utf-8', 'confidence': 0.99}),\
- patch('openlp.plugins.bibles.lib.importers.csvbible.open', create=True),\
+ patch('openlp.plugins.bibles.lib.importers.csvbible.Path.open', create=True),\
patch('openlp.plugins.bibles.lib.importers.csvbible.csv.reader', side_effect=csv.Error):
# WHEN: Calling CSVBible.parse_csv_file
# THEN: A ValidationError should be raised
with self.assertRaises(ValidationError) as context:
- CSVBible.parse_csv_file('file.csv', None)
+ CSVBible.parse_csv_file(Path('file.csv'), None)
self.assertEqual(context.exception.msg, 'Parsing "file.csv" failed')
def test_process_books_stopped_import(self):
@@ -185,7 +187,8 @@ class TestCSVImport(TestCase):
# GIVEN: An instance of CSVBible with the stop_import_flag set to True
mocked_manager = MagicMock()
with patch('openlp.plugins.bibles.lib.db.BibleDB._setup'):
- importer = CSVBible(mocked_manager, path='.', name='.', booksfile='books.csv', versefile='verse.csv')
+ importer = CSVBible(mocked_manager, path='.', name='.', books_path=Path('books.csv'),
+ verse_path=Path('verse.csv'))
type(importer).application = PropertyMock()
importer.stop_import_flag = True
importer.wizard = MagicMock()
@@ -205,7 +208,8 @@ class TestCSVImport(TestCase):
mocked_manager = MagicMock()
with patch('openlp.plugins.bibles.lib.db.BibleDB._setup'),\
patch('openlp.plugins.bibles.lib.importers.csvbible.translate'):
- importer = CSVBible(mocked_manager, path='.', name='.', booksfile='books.csv', versefile='verse.csv')
+ importer = CSVBible(mocked_manager, path='.', name='.', books_path=Path('books.csv'),
+ verse_path=Path('verse.csv'))
importer.find_and_create_book = MagicMock()
importer.language_id = 10
importer.stop_import_flag = False
@@ -229,7 +233,8 @@ class TestCSVImport(TestCase):
# GIVEN: An instance of CSVBible with the stop_import_flag set to True
mocked_manager = MagicMock()
with patch('openlp.plugins.bibles.lib.db.BibleDB._setup'):
- importer = CSVBible(mocked_manager, path='.', name='.', booksfile='books.csv', versefile='verse.csv')
+ importer = CSVBible(mocked_manager, path='.', name='.', books_path=Path('books.csv'),
+ verse_path=Path('verse.csv'))
importer.get_book_name = MagicMock()
importer.session = MagicMock()
importer.stop_import_flag = True
@@ -250,7 +255,8 @@ class TestCSVImport(TestCase):
mocked_manager = MagicMock()
with patch('openlp.plugins.bibles.lib.db.BibleDB._setup'),\
patch('openlp.plugins.bibles.lib.importers.csvbible.translate'):
- importer = CSVBible(mocked_manager, path='.', name='.', booksfile='books.csv', versefile='verse.csv')
+ importer = CSVBible(mocked_manager, path='.', name='.', books_path=Path('books.csv'),
+ verse_path=Path('verse.csv'))
importer.create_verse = MagicMock()
importer.get_book = MagicMock(return_value=Book('1', '1', '1. Mosebog', '1Mos'))
importer.get_book_name = MagicMock(return_value='1. Mosebog')
@@ -281,7 +287,8 @@ class TestCSVImport(TestCase):
# GIVEN: An instance of CSVBible and a mocked get_language which simulates the user cancelling the language box
mocked_manager = MagicMock()
with patch('openlp.plugins.bibles.lib.db.BibleDB._setup'):
- importer = CSVBible(mocked_manager, path='.', name='.', booksfile='books.csv', versefile='verse.csv')
+ importer = CSVBible(mocked_manager, path='.', name='.', books_path=Path('books.csv'),
+ verse_path=Path('verse.csv'))
importer.get_language = MagicMock(return_value=None)
# WHEN: Calling do_import
@@ -298,7 +305,8 @@ class TestCSVImport(TestCase):
# GIVEN: An instance of CSVBible
mocked_manager = MagicMock()
with patch('openlp.plugins.bibles.lib.db.BibleDB._setup'):
- importer = CSVBible(mocked_manager, path='.', name='.', booksfile='books.csv', versefile='verses.csv')
+ importer = CSVBible(mocked_manager, path='.', name='.', books_path=Path('books.csv'),
+ verse_path=Path('verses.csv'))
importer.get_language = MagicMock(return_value=10)
importer.parse_csv_file = MagicMock(side_effect=[['Book 1'], ['Verse 1']])
importer.process_books = MagicMock(return_value=['Book 1'])
@@ -312,7 +320,8 @@ class TestCSVImport(TestCase):
# THEN: parse_csv_file should be called twice,
# and True should be returned.
- self.assertEqual(importer.parse_csv_file.mock_calls, [call('books.csv', Book), call('verses.csv', Verse)])
+ self.assertEqual(importer.parse_csv_file.mock_calls,
+ [call(Path('books.csv'), Book), call(Path('verses.csv'), Verse)])
importer.process_books.assert_called_once_with(['Book 1'])
importer.process_verses.assert_called_once_with(['Verse 1'], ['Book 1'])
self.assertTrue(result)
@@ -325,12 +334,12 @@ class TestCSVImport(TestCase):
# get_book_ref_id_by_name, create_verse, create_book, session and get_language.
result_file = open(os.path.join(TEST_PATH, 'dk1933.json'), 'rb')
test_data = json.loads(result_file.read().decode())
- books_file = os.path.join(TEST_PATH, 'dk1933-books.csv')
- verses_file = os.path.join(TEST_PATH, 'dk1933-verses.csv')
+ books_file = Path(TEST_PATH, 'dk1933-books.csv')
+ verses_file = Path(TEST_PATH, 'dk1933-verses.csv')
with patch('openlp.plugins.bibles.lib.importers.csvbible.CSVBible.application'):
mocked_manager = MagicMock()
mocked_import_wizard = MagicMock()
- importer = CSVBible(mocked_manager, path='.', name='.', booksfile=books_file, versefile=verses_file)
+ importer = CSVBible(mocked_manager, path='.', name='.', books_path=books_file, verse_path=verses_file)
importer.wizard = mocked_import_wizard
importer.get_book_ref_id_by_name = MagicMock()
importer.create_verse = MagicMock()
diff --git a/tests/functional/openlp_plugins/bibles/test_opensongimport.py b/tests/functional/openlp_plugins/bibles/test_opensongimport.py
index 0e2ba8f70..eab4d33a9 100644
--- a/tests/functional/openlp_plugins/bibles/test_opensongimport.py
+++ b/tests/functional/openlp_plugins/bibles/test_opensongimport.py
@@ -29,6 +29,7 @@ from unittest.mock import MagicMock, patch, call
from lxml import objectify
+from openlp.core.common.path import Path
from openlp.core.common.registry import Registry
from openlp.plugins.bibles.lib.importers.opensong import OpenSongBible, get_text, parse_chapter_number
from openlp.plugins.bibles.lib.bibleimport import BibleImport
@@ -64,7 +65,7 @@ class TestOpenSongImport(TestCase, TestMixin):
mocked_manager = MagicMock()
# WHEN: An importer object is created
- importer = OpenSongBible(mocked_manager, path='.', name='.', filename='')
+ importer = OpenSongBible(mocked_manager, path='.', name='.', file_path=None)
# THEN: The importer should be an instance of BibleDB
self.assertIsInstance(importer, BibleImport)
@@ -126,7 +127,7 @@ class TestOpenSongImport(TestCase, TestMixin):
Test parse_verse_number when supplied with a valid verse number
"""
# GIVEN: An instance of OpenSongBible, the number 15 represented as a string and an instance of OpenSongBible
- importer = OpenSongBible(MagicMock(), path='.', name='.', filename='')
+ importer = OpenSongBible(MagicMock(), path='.', name='.', file_path=None)
# WHEN: Calling parse_verse_number
result = importer.parse_verse_number('15', 0)
@@ -139,7 +140,7 @@ class TestOpenSongImport(TestCase, TestMixin):
Test parse_verse_number when supplied with a verse range
"""
# GIVEN: An instance of OpenSongBible, and the range 24-26 represented as a string
- importer = OpenSongBible(MagicMock(), path='.', name='.', filename='')
+ importer = OpenSongBible(MagicMock(), path='.', name='.', file_path=None)
# WHEN: Calling parse_verse_number
result = importer.parse_verse_number('24-26', 0)
@@ -152,7 +153,7 @@ class TestOpenSongImport(TestCase, TestMixin):
Test parse_verse_number when supplied with a invalid verse number
"""
# GIVEN: An instance of OpenSongBible, a non numeric string represented as a string
- importer = OpenSongBible(MagicMock(), path='.', name='.', filename='')
+ importer = OpenSongBible(MagicMock(), path='.', name='.', file_path=None)
# WHEN: Calling parse_verse_number
result = importer.parse_verse_number('invalid', 41)
@@ -165,7 +166,7 @@ class TestOpenSongImport(TestCase, TestMixin):
Test parse_verse_number when the verse number is an empty string. (Bug #1074727)
"""
# GIVEN: An instance of OpenSongBible, an empty string, and the previous verse number set as 14
- importer = OpenSongBible(MagicMock(), path='.', name='.', filename='')
+ importer = OpenSongBible(MagicMock(), path='.', name='.', file_path=None)
# WHEN: Calling parse_verse_number
result = importer.parse_verse_number('', 14)
@@ -178,7 +179,7 @@ class TestOpenSongImport(TestCase, TestMixin):
"""
with patch.object(OpenSongBible, 'log_warning')as mocked_log_warning:
# GIVEN: An instanceofOpenSongBible, a Tuple, and the previous verse number set as 12
- importer = OpenSongBible(MagicMock(), path='.', name='.', filename='')
+ importer = OpenSongBible(MagicMock(), path='.', name='.', file_path=None)
# WHEN: Calling parse_verse_number
result = importer.parse_verse_number((1, 2, 3), 12)
@@ -193,7 +194,7 @@ class TestOpenSongImport(TestCase, TestMixin):
Test process_books when stop_import is set to True
"""
# GIVEN: An instance of OpenSongBible
- importer = OpenSongBible(MagicMock(), path='.', name='.', filename='')
+ importer = OpenSongBible(MagicMock(), path='.', name='.', file_path=None)
# WHEN: stop_import_flag is set to True
importer.stop_import_flag = True
@@ -209,7 +210,7 @@ class TestOpenSongImport(TestCase, TestMixin):
# GIVEN: An instance of OpenSongBible Importer and two mocked books
self.mocked_find_and_create_book.side_effect = ['db_book1', 'db_book2']
with patch.object(OpenSongBible, 'process_chapters') as mocked_process_chapters:
- importer = OpenSongBible(MagicMock(), path='.', name='.', filename='')
+ importer = OpenSongBible(MagicMock(), path='.', name='.', file_path=None)
book1 = MagicMock()
book1.attrib = {'n': 'Name1'}
@@ -236,7 +237,7 @@ class TestOpenSongImport(TestCase, TestMixin):
Test process_chapters when stop_import is set to True
"""
# GIVEN: An isntance of OpenSongBible
- importer = OpenSongBible(MagicMock(), path='.', name='.', filename='')
+ importer = OpenSongBible(MagicMock(), path='.', name='.', file_path=None)
importer.parse_chapter_number = MagicMock()
# WHEN: stop_import_flag is set to True
@@ -252,7 +253,7 @@ class TestOpenSongImport(TestCase, TestMixin):
Test process_chapters when it completes
"""
# GIVEN: An instance of OpenSongBible
- importer = OpenSongBible(MagicMock(), path='.', name='.', filename='')
+ importer = OpenSongBible(MagicMock(), path='.', name='.', file_path=None)
importer.wizard = MagicMock()
# WHEN: called with some valid data
@@ -284,7 +285,7 @@ class TestOpenSongImport(TestCase, TestMixin):
Test process_verses when stop_import is set to True
"""
# GIVEN: An isntance of OpenSongBible
- importer = OpenSongBible(MagicMock(), path='.', name='.', filename='')
+ importer = OpenSongBible(MagicMock(), path='.', name='.', file_path=None)
importer.parse_verse_number = MagicMock()
# WHEN: stop_import_flag is set to True
@@ -303,7 +304,7 @@ class TestOpenSongImport(TestCase, TestMixin):
patch.object(OpenSongBible, 'parse_verse_number',
**{'side_effect': [1, 2]}) as mocked_parse_verse_number:
# GIVEN: An instance of OpenSongBible
- importer = OpenSongBible(MagicMock(), path='.', name='.', filename='')
+ importer = OpenSongBible(MagicMock(), path='.', name='.', file_path=None)
importer.wizard = MagicMock()
# WHEN: called with some valid data
@@ -338,7 +339,7 @@ class TestOpenSongImport(TestCase, TestMixin):
patch.object(OpenSongBible, 'validate_xml_file'), \
patch.object(OpenSongBible, 'parse_xml', return_value=None), \
patch.object(OpenSongBible, 'get_language_id') as mocked_language_id:
- importer = OpenSongBible(MagicMock(), path='.', name='.', filename='')
+ importer = OpenSongBible(MagicMock(), path='.', name='.', file_path=None)
# WHEN: Calling do_import
result = importer.do_import()
@@ -357,7 +358,7 @@ class TestOpenSongImport(TestCase, TestMixin):
patch.object(OpenSongBible, 'parse_xml'), \
patch.object(OpenSongBible, 'get_language_id', return_value=False), \
patch.object(OpenSongBible, 'process_books') as mocked_process_books:
- importer = OpenSongBible(MagicMock(), path='.', name='.', filename='')
+ importer = OpenSongBible(MagicMock(), path='.', name='.', file_path=None)
# WHEN: Calling do_import
result = importer.do_import()
@@ -376,7 +377,7 @@ class TestOpenSongImport(TestCase, TestMixin):
patch.object(OpenSongBible, 'parse_xml'), \
patch.object(OpenSongBible, 'get_language_id', return_value=10), \
patch.object(OpenSongBible, 'process_books'):
- importer = OpenSongBible(MagicMock(), path='.', name='.', filename='')
+ importer = OpenSongBible(MagicMock(), path='.', name='.', file_path=None)
# WHEN: Calling do_import
result = importer.do_import()
@@ -406,7 +407,7 @@ class TestOpenSongImportFileImports(TestCase, TestMixin):
with patch('openlp.plugins.bibles.lib.importers.opensong.OpenSongBible.application'):
mocked_manager = MagicMock()
mocked_import_wizard = MagicMock()
- importer = OpenSongBible(mocked_manager, path='.', name='.', filename='')
+ importer = OpenSongBible(mocked_manager, path='.', name='.', file_path=None)
importer.wizard = mocked_import_wizard
importer.get_book_ref_id_by_name = MagicMock()
importer.create_verse = MagicMock()
@@ -416,7 +417,7 @@ class TestOpenSongImportFileImports(TestCase, TestMixin):
importer.get_language.return_value = 'Danish'
# WHEN: Importing bible file
- importer.filename = os.path.join(TEST_PATH, bible_file)
+ importer.file_path = Path(TEST_PATH, bible_file)
importer.do_import()
# THEN: The create_verse() method should have been called with each verse in the file.
diff --git a/tests/functional/openlp_plugins/bibles/test_osisimport.py b/tests/functional/openlp_plugins/bibles/test_osisimport.py
index a456d71f6..02c6c3654 100644
--- a/tests/functional/openlp_plugins/bibles/test_osisimport.py
+++ b/tests/functional/openlp_plugins/bibles/test_osisimport.py
@@ -27,6 +27,7 @@ import json
from unittest import TestCase
from unittest.mock import MagicMock, call, patch
+from openlp.core.common.path import Path
from openlp.plugins.bibles.lib.bibleimport import BibleImport
from openlp.plugins.bibles.lib.db import BibleDB
from openlp.plugins.bibles.lib.importers.osis import OSISBible
@@ -63,7 +64,7 @@ class TestOsisImport(TestCase):
mocked_manager = MagicMock()
# WHEN: An importer object is created
- importer = OSISBible(mocked_manager, path='.', name='.', filename='')
+ importer = OSISBible(mocked_manager, path='.', name='.', file_path=None)
# THEN: The importer should be an instance of BibleDB
self.assertIsInstance(importer, BibleDB)
@@ -73,7 +74,7 @@ class TestOsisImport(TestCase):
Test process_books when stop_import is set to True
"""
# GIVEN: An instance of OSISBible adn some mocked data
- importer = OSISBible(MagicMock(), path='.', name='.', filename='')
+ importer = OSISBible(MagicMock(), path='.', name='.', file_path=None)
mocked_data = MagicMock(**{'xpath.return_value': ['Book']})
# WHEN: stop_import_flag is set to True and process_books is called
@@ -90,7 +91,7 @@ class TestOsisImport(TestCase):
# GIVEN: An instance of OSISBible Importer and two mocked books
self.mocked_find_and_create_book.side_effect = ['db_book1', 'db_book2']
with patch.object(OSISBible, 'process_chapters') as mocked_process_chapters:
- importer = OSISBible(MagicMock(), path='.', name='.', filename='')
+ importer = OSISBible(MagicMock(), path='.', name='.', file_path=None)
book1 = MagicMock()
book1.get.return_value = 'Name1'
@@ -128,7 +129,7 @@ class TestOsisImport(TestCase):
test_chapter = MagicMock()
test_chapter.__iter__.return_value = [test_verse]
test_chapter.get.side_effect = lambda x: {'osisID': '1.2.4', 'sID': '999'}.get(x)
- importer = OSISBible(MagicMock(), path='.', name='.', filename='')
+ importer = OSISBible(MagicMock(), path='.', name='.', file_path=None)
# WHEN: Calling process_chapters
importer.process_chapters(test_book, [test_chapter])
@@ -155,7 +156,7 @@ class TestOsisImport(TestCase):
test_chapter = MagicMock()
test_chapter.__iter__.return_value = [test_verse]
test_chapter.get.side_effect = lambda x: {'osisID': '1.2.4', 'sID': '999'}.get(x)
- importer = OSISBible(MagicMock(), path='.', name='.', filename='')
+ importer = OSISBible(MagicMock(), path='.', name='.', file_path=None)
# WHEN: Calling process_chapters
importer.process_chapters(test_book, [test_chapter])
@@ -180,7 +181,7 @@ class TestOsisImport(TestCase):
test_chapter.get.side_effect = lambda x: {'osisID': '1.2.4'}.get(x)
# WHEN: Calling process_chapters
- importer = OSISBible(MagicMock(), path='.', name='.', filename='')
+ importer = OSISBible(MagicMock(), path='.', name='.', file_path=None)
importer.process_chapters(test_book, [test_chapter])
# THEN: neither set_current_chapter or process_verse should have been called
@@ -201,7 +202,7 @@ class TestOsisImport(TestCase):
test_chapter = MagicMock()
test_chapter.tag = '{http://www.bibletechnologies.net/2003/OSIS/namespace}chapter'
test_chapter.get.side_effect = lambda x: {'osisID': '1.2.4', 'sID': '999'}.get(x)
- importer = OSISBible(MagicMock(), path='.', name='.', filename='')
+ importer = OSISBible(MagicMock(), path='.', name='.', file_path=None)
# WHEN: Calling process_chapters
importer.process_chapters(test_book, [test_chapter])
@@ -228,7 +229,7 @@ class TestOsisImport(TestCase):
test_verse.text = 'Verse Text'
# WHEN: Calling process_chapters
- importer = OSISBible(MagicMock(), path='.', name='.', filename='')
+ importer = OSISBible(MagicMock(), path='.', name='.', file_path=None)
importer.process_chapters(test_book, [test_verse])
# THEN: process_verse should have been called with the test data
@@ -245,7 +246,7 @@ class TestOsisImport(TestCase):
test_verse.get.side_effect = lambda x: {}.get(x)
test_verse.tail = 'Verse Text'
test_verse.text = None
- importer = OSISBible(MagicMock(), path='.', name='.', filename='')
+ importer = OSISBible(MagicMock(), path='.', name='.', file_path=None)
# WHEN: Calling process_verse with the test data
importer.process_verse(test_book, 2, test_verse)
@@ -264,7 +265,7 @@ class TestOsisImport(TestCase):
test_verse.get.side_effect = lambda x: {}.get(x)
test_verse.tail = 'Verse Text'
test_verse.text = None
- importer = OSISBible(MagicMock(), path='.', name='.', filename='')
+ importer = OSISBible(MagicMock(), path='.', name='.', file_path=None)
# WHEN: Calling process_verse with the test data
importer.process_verse(test_book, 2, test_verse)
@@ -282,7 +283,7 @@ class TestOsisImport(TestCase):
test_verse.tail = None
test_verse.text = None
test_verse.get.side_effect = lambda x: {'osisID': '1.2.4', 'sID': '999'}.get(x)
- importer = OSISBible(MagicMock(), path='.', name='.', filename='')
+ importer = OSISBible(MagicMock(), path='.', name='.', file_path=None)
# WHEN: Calling process_verse with the test data
importer.process_verse(test_book, 2, test_verse, use_milestones=True)
@@ -301,7 +302,7 @@ class TestOsisImport(TestCase):
test_verse.tail = 'Verse Text'
test_verse.text = None
test_verse.get.side_effect = lambda x: {'osisID': '1.2.4', 'sID': '999'}.get(x)
- importer = OSISBible(MagicMock(), path='.', name='.', filename='')
+ importer = OSISBible(MagicMock(), path='.', name='.', file_path=None)
# WHEN: Calling process_verse with the test data
importer.process_verse(test_book, 2, test_verse, use_milestones=True)
@@ -320,7 +321,7 @@ class TestOsisImport(TestCase):
test_verse.tail = '\n ' # Whitespace
test_verse.text = None
test_verse.get.side_effect = lambda x: {'osisID': '1.2.4', 'sID': '999'}.get(x)
- importer = OSISBible(MagicMock(), path='.', name='.', filename='')
+ importer = OSISBible(MagicMock(), path='.', name='.', file_path=None)
# WHEN: Calling process_verse with the test data
importer.process_verse(test_book, 2, test_verse)
@@ -339,7 +340,7 @@ class TestOsisImport(TestCase):
test_verse.tail = '\n ' # Whitespace
test_verse.text = 'Verse Text'
test_verse.get.side_effect = lambda x: {'osisID': '1.2.4', 'sID': '999'}.get(x)
- importer = OSISBible(MagicMock(), path='.', name='.', filename='')
+ importer = OSISBible(MagicMock(), path='.', name='.', file_path=None)
# WHEN: Calling process_verse with the test data
importer.process_verse(test_book, 2, test_verse)
@@ -356,7 +357,7 @@ class TestOsisImport(TestCase):
patch.object(OSISBible, 'validate_xml_file'), \
patch.object(OSISBible, 'parse_xml', return_value=None), \
patch.object(OSISBible, 'get_language_id') as mocked_language_id:
- importer = OSISBible(MagicMock(), path='.', name='.', filename='')
+ importer = OSISBible(MagicMock(), path='.', name='.', file_path=None)
# WHEN: Calling do_import
result = importer.do_import()
@@ -375,7 +376,7 @@ class TestOsisImport(TestCase):
patch.object(OSISBible, 'parse_xml'), \
patch.object(OSISBible, 'get_language_id', **{'return_value': False}), \
patch.object(OSISBible, 'process_books') as mocked_process_books:
- importer = OSISBible(MagicMock(), path='.', name='.', filename='')
+ importer = OSISBible(MagicMock(), path='.', name='.', file_path=None)
# WHEN: Calling do_import
result = importer.do_import()
@@ -394,7 +395,7 @@ class TestOsisImport(TestCase):
patch.object(OSISBible, 'parse_xml'), \
patch.object(OSISBible, 'get_language_id', **{'return_value': 10}), \
patch.object(OSISBible, 'process_books'):
- importer = OSISBible(MagicMock(), path='.', name='.', filename='')
+ importer = OSISBible(MagicMock(), path='.', name='.', file_path=None)
# WHEN: Calling do_import
result = importer.do_import()
@@ -427,7 +428,7 @@ class TestOsisImportFileImports(TestCase):
with patch('openlp.plugins.bibles.lib.importers.osis.OSISBible.application'):
mocked_manager = MagicMock()
mocked_import_wizard = MagicMock()
- importer = OSISBible(mocked_manager, path='.', name='.', filename='')
+ importer = OSISBible(mocked_manager, path='.', name='.', file_path=None)
importer.wizard = mocked_import_wizard
importer.get_book_ref_id_by_name = MagicMock()
importer.create_verse = MagicMock()
@@ -437,7 +438,7 @@ class TestOsisImportFileImports(TestCase):
importer.get_language.return_value = 'Danish'
# WHEN: Importing bible file
- importer.filename = os.path.join(TEST_PATH, bible_file)
+ importer.file_path = Path(TEST_PATH, bible_file)
importer.do_import()
# THEN: The create_verse() method should have been called with each verse in the file.
@@ -457,7 +458,7 @@ class TestOsisImportFileImports(TestCase):
with patch('openlp.plugins.bibles.lib.importers.osis.OSISBible.application'):
mocked_manager = MagicMock()
mocked_import_wizard = MagicMock()
- importer = OSISBible(mocked_manager, path='.', name='.', filename='')
+ importer = OSISBible(mocked_manager, path='.', name='.', file_path=None)
importer.wizard = mocked_import_wizard
importer.get_book_ref_id_by_name = MagicMock()
importer.create_verse = MagicMock()
@@ -467,7 +468,7 @@ class TestOsisImportFileImports(TestCase):
importer.get_language.return_value = 'English'
# WHEN: Importing bible file
- importer.filename = os.path.join(TEST_PATH, bible_file)
+ importer.file_path = Path(TEST_PATH, bible_file)
importer.do_import()
# THEN: The create_verse() method should have been called with each verse in the file.
@@ -487,7 +488,7 @@ class TestOsisImportFileImports(TestCase):
with patch('openlp.plugins.bibles.lib.importers.osis.OSISBible.application'):
mocked_manager = MagicMock()
mocked_import_wizard = MagicMock()
- importer = OSISBible(mocked_manager, path='.', name='.', filename='')
+ importer = OSISBible(mocked_manager, path='.', name='.', file_path=None)
importer.wizard = mocked_import_wizard
importer.get_book_ref_id_by_name = MagicMock()
importer.create_verse = MagicMock()
@@ -497,7 +498,7 @@ class TestOsisImportFileImports(TestCase):
importer.get_language.return_value = 'English'
# WHEN: Importing bible file
- importer.filename = os.path.join(TEST_PATH, bible_file)
+ importer.file_path = Path(TEST_PATH, bible_file)
importer.do_import()
# THEN: The create_verse() method should have been called with each verse in the file.
@@ -517,7 +518,7 @@ class TestOsisImportFileImports(TestCase):
with patch('openlp.plugins.bibles.lib.importers.osis.OSISBible.application'):
mocked_manager = MagicMock()
mocked_import_wizard = MagicMock()
- importer = OSISBible(mocked_manager, path='.', name='.', filename='')
+ importer = OSISBible(mocked_manager, path='.', name='.', file_path=None)
importer.wizard = mocked_import_wizard
importer.get_book_ref_id_by_name = MagicMock()
importer.create_verse = MagicMock()
@@ -527,7 +528,7 @@ class TestOsisImportFileImports(TestCase):
importer.get_language.return_value = 'Danish'
# WHEN: Importing bible file
- importer.filename = os.path.join(TEST_PATH, bible_file)
+ importer.file_path = Path(TEST_PATH, bible_file)
importer.do_import()
# THEN: The create_verse() method should have been called with each verse in the file.
diff --git a/tests/functional/openlp_plugins/bibles/test_swordimport.py b/tests/functional/openlp_plugins/bibles/test_swordimport.py
index 235beea58..34e011498 100644
--- a/tests/functional/openlp_plugins/bibles/test_swordimport.py
+++ b/tests/functional/openlp_plugins/bibles/test_swordimport.py
@@ -64,7 +64,7 @@ class TestSwordImport(TestCase):
mocked_manager = MagicMock()
# WHEN: An importer object is created
- importer = SwordBible(mocked_manager, path='.', name='.', filename='', sword_key='', sword_path='')
+ importer = SwordBible(mocked_manager, path='.', name='.', file_path=None, sword_key='', sword_path='')
# THEN: The importer should be an instance of BibleDB
self.assertIsInstance(importer, BibleDB)
@@ -80,7 +80,7 @@ class TestSwordImport(TestCase):
# Also mocked pysword structures
mocked_manager = MagicMock()
mocked_import_wizard = MagicMock()
- importer = SwordBible(mocked_manager, path='.', name='.', filename='', sword_key='', sword_path='')
+ importer = SwordBible(mocked_manager, path='.', name='.', file_path=None, sword_key='', sword_path='')
result_file = open(os.path.join(TEST_PATH, 'dk1933.json'), 'rb')
test_data = json.loads(result_file.read().decode())
importer.wizard = mocked_import_wizard
diff --git a/tests/functional/openlp_plugins/bibles/test_wordprojectimport.py b/tests/functional/openlp_plugins/bibles/test_wordprojectimport.py
index 6e62dae9e..fbf5b0412 100644
--- a/tests/functional/openlp_plugins/bibles/test_wordprojectimport.py
+++ b/tests/functional/openlp_plugins/bibles/test_wordprojectimport.py
@@ -26,6 +26,7 @@ import os
from unittest import TestCase
from unittest.mock import MagicMock, patch, call
+from openlp.core.common.path import Path
from openlp.plugins.bibles.lib.importers.wordproject import WordProjectBible
@@ -48,19 +49,17 @@ class TestWordProjectImport(TestCase):
self.addCleanup(self.manager_patcher.stop)
self.manager_patcher.start()
- @patch('openlp.plugins.bibles.lib.importers.wordproject.os')
- @patch('openlp.plugins.bibles.lib.importers.wordproject.copen')
- def test_process_books(self, mocked_open, mocked_os):
+ @patch.object(Path, 'read_text')
+ def test_process_books(self, mocked_read_text):
"""
Test the process_books() method
"""
# GIVEN: A WordProject importer and a bunch of mocked things
- importer = WordProjectBible(MagicMock(), path='.', name='.', filename='kj.zip')
- importer.base_dir = ''
+ importer = WordProjectBible(MagicMock(), path='.', name='.', file_path=Path('kj.zip'))
+ importer.base_path = Path()
importer.stop_import_flag = False
importer.language_id = 'en'
- mocked_open.return_value.__enter__.return_value.read.return_value = INDEX_PAGE
- mocked_os.path.join.side_effect = lambda *x: ''.join(x)
+ mocked_read_text.return_value = INDEX_PAGE
# WHEN: process_books() is called
with patch.object(importer, 'find_and_create_book') as mocked_find_and_create_book, \
@@ -69,26 +68,22 @@ class TestWordProjectImport(TestCase):
importer.process_books()
# THEN: The right methods should have been called
- mocked_os.path.join.assert_called_once_with('', 'index.htm')
- mocked_open.assert_called_once_with('index.htm', encoding='utf-8', errors='ignore')
+ mocked_read_text.assert_called_once_with(encoding='utf-8', errors='ignore')
assert mocked_find_and_create_book.call_count == 66, 'There should be 66 books'
assert mocked_process_chapters.call_count == 66, 'There should be 66 books'
assert mocked_session.commit.call_count == 66, 'There should be 66 books'
- @patch('openlp.plugins.bibles.lib.importers.wordproject.os')
- @patch('openlp.plugins.bibles.lib.importers.wordproject.copen')
- def test_process_chapters(self, mocked_open, mocked_os):
+ @patch.object(Path, 'read_text')
+ def test_process_chapters(self, mocked_read_text):
"""
Test the process_chapters() method
"""
# GIVEN: A WordProject importer and a bunch of mocked things
- importer = WordProjectBible(MagicMock(), path='.', name='.', filename='kj.zip')
- importer.base_dir = ''
+ importer = WordProjectBible(MagicMock(), path='.', name='.', file_path=Path('kj.zip'))
+ importer.base_path = Path()
importer.stop_import_flag = False
importer.language_id = 'en'
- mocked_open.return_value.__enter__.return_value.read.return_value = CHAPTER_PAGE
- mocked_os.path.join.side_effect = lambda *x: ''.join(x)
- mocked_os.path.normpath.side_effect = lambda x: x
+ mocked_read_text.return_value = CHAPTER_PAGE
mocked_db_book = MagicMock()
mocked_db_book.name = 'Genesis'
book_id = 1
@@ -102,24 +97,21 @@ class TestWordProjectImport(TestCase):
# THEN: The right methods should have been called
expected_set_current_chapter_calls = [call('Genesis', ch) for ch in range(1, 51)]
expected_process_verses_calls = [call(mocked_db_book, 1, ch) for ch in range(1, 51)]
- mocked_os.path.join.assert_called_once_with('', '01/1.htm')
- mocked_open.assert_called_once_with('01/1.htm', encoding='utf-8', errors='ignore')
+ mocked_read_text.assert_called_once_with(encoding='utf-8', errors='ignore')
assert mocked_set_current_chapter.call_args_list == expected_set_current_chapter_calls
assert mocked_process_verses.call_args_list == expected_process_verses_calls
- @patch('openlp.plugins.bibles.lib.importers.wordproject.os')
- @patch('openlp.plugins.bibles.lib.importers.wordproject.copen')
- def test_process_verses(self, mocked_open, mocked_os):
+ @patch.object(Path, 'read_text')
+ def test_process_verses(self, mocked_read_text):
"""
Test the process_verses() method
"""
# GIVEN: A WordProject importer and a bunch of mocked things
- importer = WordProjectBible(MagicMock(), path='.', name='.', filename='kj.zip')
- importer.base_dir = ''
+ importer = WordProjectBible(MagicMock(), path='.', name='.', file_path=Path('kj.zip'))
+ importer.base_path = Path()
importer.stop_import_flag = False
importer.language_id = 'en'
- mocked_open.return_value.__enter__.return_value.read.return_value = CHAPTER_PAGE
- mocked_os.path.join.side_effect = lambda *x: '/'.join(x)
+ mocked_read_text.return_value = CHAPTER_PAGE
mocked_db_book = MagicMock()
mocked_db_book.name = 'Genesis'
book_number = 1
@@ -130,8 +122,7 @@ class TestWordProjectImport(TestCase):
importer.process_verses(mocked_db_book, book_number, chapter_number)
# THEN: All the right methods should have been called
- mocked_os.path.join.assert_called_once_with('', '01', '1.htm')
- mocked_open.assert_called_once_with('/01/1.htm', encoding='utf-8', errors='ignore')
+ mocked_read_text.assert_called_once_with(encoding='utf-8', errors='ignore')
assert mocked_process_verse.call_count == 31
def test_process_verse(self):
@@ -139,7 +130,7 @@ class TestWordProjectImport(TestCase):
Test the process_verse() method
"""
# GIVEN: An importer and a mocked method
- importer = WordProjectBible(MagicMock(), path='.', name='.', filename='kj.zip')
+ importer = WordProjectBible(MagicMock(), path='.', name='.', file_path=Path('kj.zip'))
mocked_db_book = MagicMock()
mocked_db_book.id = 1
chapter_number = 1
@@ -158,7 +149,7 @@ class TestWordProjectImport(TestCase):
Test the process_verse() method when there's no text
"""
# GIVEN: An importer and a mocked method
- importer = WordProjectBible(MagicMock(), path='.', name='.', filename='kj.zip')
+ importer = WordProjectBible(MagicMock(), path='.', name='.', file_path=Path('kj.zip'))
mocked_db_book = MagicMock()
mocked_db_book.id = 1
chapter_number = 1
@@ -177,7 +168,7 @@ class TestWordProjectImport(TestCase):
Test the do_import() method
"""
# GIVEN: An importer and mocked methods
- importer = WordProjectBible(MagicMock(), path='.', name='.', filename='kj.zip')
+ importer = WordProjectBible(MagicMock(), path='.', name='.', file_path='kj.zip')
# WHEN: do_import() is called
with patch.object(importer, '_unzip_file') as mocked_unzip_file, \
@@ -199,7 +190,7 @@ class TestWordProjectImport(TestCase):
Test the do_import() method when the language is not available
"""
# GIVEN: An importer and mocked methods
- importer = WordProjectBible(MagicMock(), path='.', name='.', filename='kj.zip')
+ importer = WordProjectBible(MagicMock(), path='.', name='.', file_path='kj.zip')
# WHEN: do_import() is called
with patch.object(importer, '_unzip_file') as mocked_unzip_file, \
diff --git a/tests/functional/openlp_plugins/bibles/test_zefaniaimport.py b/tests/functional/openlp_plugins/bibles/test_zefaniaimport.py
index 8e43d55b5..d423a2153 100644
--- a/tests/functional/openlp_plugins/bibles/test_zefaniaimport.py
+++ b/tests/functional/openlp_plugins/bibles/test_zefaniaimport.py
@@ -27,6 +27,7 @@ import json
from unittest import TestCase
from unittest.mock import MagicMock, patch
+from openlp.core.common.path import Path
from openlp.plugins.bibles.lib.importers.zefania import ZefaniaBible
from openlp.plugins.bibles.lib.db import BibleDB
@@ -55,7 +56,7 @@ class TestZefaniaImport(TestCase):
mocked_manager = MagicMock()
# WHEN: An importer object is created
- importer = ZefaniaBible(mocked_manager, path='.', name='.', filename='')
+ importer = ZefaniaBible(mocked_manager, path='.', name='.', file_path=None)
# THEN: The importer should be an instance of BibleDB
self.assertIsInstance(importer, BibleDB)
@@ -72,7 +73,7 @@ class TestZefaniaImport(TestCase):
with patch('openlp.plugins.bibles.lib.importers.zefania.ZefaniaBible.application'):
mocked_manager = MagicMock()
mocked_import_wizard = MagicMock()
- importer = ZefaniaBible(mocked_manager, path='.', name='.', filename='')
+ importer = ZefaniaBible(mocked_manager, path='.', name='.', file_path=None)
importer.wizard = mocked_import_wizard
importer.create_verse = MagicMock()
importer.create_book = MagicMock()
@@ -81,7 +82,7 @@ class TestZefaniaImport(TestCase):
importer.get_language.return_value = 'Danish'
# WHEN: Importing bible file
- importer.filename = os.path.join(TEST_PATH, bible_file)
+ importer.file_path = Path(TEST_PATH, bible_file)
importer.do_import()
# THEN: The create_verse() method should have been called with each verse in the file.
@@ -102,7 +103,7 @@ class TestZefaniaImport(TestCase):
with patch('openlp.plugins.bibles.lib.importers.zefania.ZefaniaBible.application'):
mocked_manager = MagicMock()
mocked_import_wizard = MagicMock()
- importer = ZefaniaBible(mocked_manager, path='.', name='.', filename='')
+ importer = ZefaniaBible(mocked_manager, path='.', name='.', file_path=None)
importer.wizard = mocked_import_wizard
importer.create_verse = MagicMock()
importer.create_book = MagicMock()
@@ -111,7 +112,7 @@ class TestZefaniaImport(TestCase):
importer.get_language.return_value = 'Russian'
# WHEN: Importing bible file
- importer.filename = os.path.join(TEST_PATH, bible_file)
+ importer.file_path = Path(TEST_PATH, bible_file)
importer.do_import()
# THEN: The create_verse() method should have been called with each verse in the file.
diff --git a/tests/functional/openlp_plugins/images/test_lib.py b/tests/functional/openlp_plugins/images/test_lib.py
index 4dfef57da..877ad722d 100644
--- a/tests/functional/openlp_plugins/images/test_lib.py
+++ b/tests/functional/openlp_plugins/images/test_lib.py
@@ -173,12 +173,14 @@ class TestImageMediaItem(TestCase):
"""
# GIVEN: A mocked version of reset_action
self.media_item.reset_action = MagicMock()
+ self.media_item.reset_action_context = MagicMock()
# WHEN: on_reset_click is called
self.media_item.on_reset_click()
# THEN: the reset_action should be set visible, and the image should be reset
self.media_item.reset_action.setVisible.assert_called_with(False)
+ self.media_item.reset_action_context.setVisible.assert_called_with(False)
self.media_item.live_controller.display.reset_image.assert_called_with()
@patch('openlp.plugins.images.lib.mediaitem.delete_file')
diff --git a/tests/functional/openlp_plugins/presentations/test_mediaitem.py b/tests/functional/openlp_plugins/presentations/test_mediaitem.py
index 1116ce4cb..fd28b9b03 100644
--- a/tests/functional/openlp_plugins/presentations/test_mediaitem.py
+++ b/tests/functional/openlp_plugins/presentations/test_mediaitem.py
@@ -133,3 +133,27 @@ class TestMediaItem(TestCase, TestMixin):
# THEN: doc.presentation_deleted should have been called since the presentation file did not exists.
mocked_doc.assert_has_calls([call.get_thumbnail_path(1, True), call.presentation_deleted()], True)
+
+ @patch('openlp.plugins.presentations.lib.mediaitem.MediaManagerItem._setup')
+ @patch('openlp.plugins.presentations.lib.mediaitem.PresentationMediaItem.setup_item')
+ @patch('openlp.plugins.presentations.lib.mediaitem.Settings')
+ def test_search(self, mocked_settings, *unreferenced_mocks):
+ """
+ Test that the search method finds the correct results
+ """
+ # GIVEN: A mocked Settings class which returns a list of Path objects,
+ # and an instance of the PresentationMediaItem
+ path_1 = Path('some_dir', 'Impress_file_1')
+ path_2 = Path('some_other_dir', 'impress_file_2')
+ path_3 = Path('another_dir', 'ppt_file')
+ mocked_returned_settings = MagicMock()
+ mocked_returned_settings.value.return_value = [path_1, path_2, path_3]
+ mocked_settings.return_value = mocked_returned_settings
+ media_item = PresentationMediaItem(None, MagicMock(), None)
+ media_item.settings_section = ''
+
+ # WHEN: Calling search
+ results = media_item.search('IMPRE', False)
+
+ # THEN: The first two results should have been returned
+ assert results == [[str(path_1), 'Impress_file_1'], [str(path_2), 'impress_file_2']]
diff --git a/tests/functional/openlp_plugins/presentations/test_pdfcontroller.py b/tests/functional/openlp_plugins/presentations/test_pdfcontroller.py
index f2ca6447b..a39eb4096 100644
--- a/tests/functional/openlp_plugins/presentations/test_pdfcontroller.py
+++ b/tests/functional/openlp_plugins/presentations/test_pdfcontroller.py
@@ -67,10 +67,10 @@ class TestPdfController(TestCase, TestMixin):
self.desktop.screenGeometry.return_value = SCREEN['size']
self.screens = ScreenList.create(self.desktop)
Settings().extend_default_settings(__default_settings__)
- self.temp_folder = Path(mkdtemp())
- self.thumbnail_folder = Path(mkdtemp())
+ self.temp_folder_path = Path(mkdtemp())
+ self.thumbnail_folder_path = Path(mkdtemp())
self.mock_plugin = MagicMock()
- self.mock_plugin.settings_section = self.temp_folder
+ self.mock_plugin.settings_section = self.temp_folder_path
def tearDown(self):
"""
@@ -78,8 +78,8 @@ class TestPdfController(TestCase, TestMixin):
"""
del self.screens
self.destroy_settings()
- shutil.rmtree(str(self.thumbnail_folder))
- shutil.rmtree(str(self.temp_folder))
+ self.thumbnail_folder_path.rmtree()
+ self.temp_folder_path.rmtree()
def test_constructor(self):
"""
@@ -105,8 +105,8 @@ class TestPdfController(TestCase, TestMixin):
controller = PdfController(plugin=self.mock_plugin)
if not controller.check_available():
raise SkipTest('Could not detect mudraw or ghostscript, so skipping PDF test')
- controller.temp_folder = self.temp_folder
- controller.thumbnail_folder = self.thumbnail_folder
+ controller.temp_folder = self.temp_folder_path
+ controller.thumbnail_folder = self.thumbnail_folder_path
document = PdfDocument(controller, test_file)
loaded = document.load_presentation()
@@ -125,14 +125,14 @@ class TestPdfController(TestCase, TestMixin):
controller = PdfController(plugin=self.mock_plugin)
if not controller.check_available():
raise SkipTest('Could not detect mudraw or ghostscript, so skipping PDF test')
- controller.temp_folder = self.temp_folder
- controller.thumbnail_folder = self.thumbnail_folder
+ controller.temp_folder = self.temp_folder_path
+ controller.thumbnail_folder = self.thumbnail_folder_path
document = PdfDocument(controller, test_file)
loaded = document.load_presentation()
# THEN: The load should succeed and pictures should be created and have been scales to fit the screen
self.assertTrue(loaded, 'The loading of the PDF should succeed.')
- image = QtGui.QImage(os.path.join(str(self.temp_folder), 'pdf_test1.pdf', 'mainslide001.png'))
+ image = QtGui.QImage(os.path.join(str(self.temp_folder_path), 'pdf_test1.pdf', 'mainslide001.png'))
# Based on the converter used the resolution will differ a bit
if controller.gsbin:
self.assertEqual(760, image.height(), 'The height should be 760')
diff --git a/tests/functional/openlp_plugins/presentations/test_presentationcontroller.py b/tests/functional/openlp_plugins/presentations/test_presentationcontroller.py
index 30ab11561..a921ef81e 100644
--- a/tests/functional/openlp_plugins/presentations/test_presentationcontroller.py
+++ b/tests/functional/openlp_plugins/presentations/test_presentationcontroller.py
@@ -144,7 +144,7 @@ class TestPresentationController(TestCase):
# GIVEN: A mocked open, get_thumbnail_folder and exists
with patch('openlp.plugins.presentations.lib.presentationcontroller.Path.read_text') as mocked_read_text, \
patch(FOLDER_TO_PATCH) as mocked_get_thumbnail_folder:
- mocked_read_text.side_effect = IOError()
+ mocked_read_text.side_effect = OSError()
mocked_get_thumbnail_folder.return_value = Path('test')
# WHEN: calling get_titles_and_notes
diff --git a/tests/functional/openlp_plugins/songs/test_openlyricsexport.py b/tests/functional/openlp_plugins/songs/test_openlyricsexport.py
index 0fd4767db..85bf0818c 100644
--- a/tests/functional/openlp_plugins/songs/test_openlyricsexport.py
+++ b/tests/functional/openlp_plugins/songs/test_openlyricsexport.py
@@ -22,13 +22,12 @@
"""
This module contains tests for the OpenLyrics song importer.
"""
-import shutil
from tempfile import mkdtemp
from unittest import TestCase
from unittest.mock import MagicMock, patch
from openlp.core.common.registry import Registry
-from openlp.core.common.path import Path, rmtree
+from openlp.core.common.path import Path
from openlp.plugins.songs.lib.openlyricsexport import OpenLyricsExport
from tests.helpers.testmixin import TestMixin
@@ -49,7 +48,7 @@ class TestOpenLyricsExport(TestCase, TestMixin):
"""
Cleanup
"""
- rmtree(self.temp_folder)
+ self.temp_folder.rmtree()
def test_export_same_filename(self):
"""
diff --git a/tests/interfaces/openlp_core/lib/test_pluginmanager.py b/tests/interfaces/openlp_core/lib/test_pluginmanager.py
index 2e7d979a6..588c15520 100644
--- a/tests/interfaces/openlp_core/lib/test_pluginmanager.py
+++ b/tests/interfaces/openlp_core/lib/test_pluginmanager.py
@@ -23,7 +23,6 @@
Package to test the openlp.core.lib.pluginmanager package.
"""
import sys
-import shutil
import gc
from tempfile import mkdtemp
from unittest import TestCase
@@ -50,8 +49,8 @@ class TestPluginManager(TestCase, TestMixin):
"""
self.setup_application()
self.build_settings()
- self.temp_dir = Path(mkdtemp('openlp'))
- Settings().setValue('advanced/data path', self.temp_dir)
+ self.temp_dir_path = Path(mkdtemp('openlp'))
+ Settings().setValue('advanced/data path', self.temp_dir_path)
Registry.create()
Registry().register('service_list', MagicMock())
self.main_window = QtWidgets.QMainWindow()
@@ -64,7 +63,7 @@ class TestPluginManager(TestCase, TestMixin):
# On windows we need to manually garbage collect to close sqlalchemy files
# to avoid errors when temporary files are deleted.
gc.collect()
- shutil.rmtree(str(self.temp_dir))
+ self.temp_dir_path.rmtree()
@patch('openlp.plugins.songusage.lib.db.init_schema')
@patch('openlp.plugins.songs.lib.db.init_schema')
diff --git a/tests/interfaces/openlp_core/ui/media/vendor/test_mediainfoWrapper.py b/tests/interfaces/openlp_core/ui/media/vendor/test_mediainfoWrapper.py
index f8fee253d..6ec2431b9 100644
--- a/tests/interfaces/openlp_core/ui/media/vendor/test_mediainfoWrapper.py
+++ b/tests/interfaces/openlp_core/ui/media/vendor/test_mediainfoWrapper.py
@@ -29,7 +29,6 @@ from unittest import TestCase
from openlp.core.ui.media.vendor.mediainfoWrapper import MediaInfoWrapper
TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..', '..', '..', 'resources', 'media'))
-
TEST_MEDIA = [['avi_file.avi', 61495], ['mp3_file.mp3', 134426], ['mpg_file.mpg', 9404], ['mp4_file.mp4', 188336]]
diff --git a/tests/interfaces/openlp_core/ui/test_projectoreditform.py b/tests/interfaces/openlp_core/ui/test_projectoreditform.py
index fc261d9f5..ec2539a29 100644
--- a/tests/interfaces/openlp_core/ui/test_projectoreditform.py
+++ b/tests/interfaces/openlp_core/ui/test_projectoreditform.py
@@ -20,7 +20,7 @@
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
-Interface tests to test the openlp.core.ui.projector.editform.ProjectorEditForm()
+Interface tests to test the openlp.core.projectors.editform.ProjectorEditForm()
class and methods.
"""
import os
@@ -28,8 +28,9 @@ from unittest import TestCase
from unittest.mock import patch
from openlp.core.common.registry import Registry
-from openlp.core.lib.projector.db import Projector, ProjectorDB
-from openlp.core.ui import ProjectorEditForm
+from openlp.core.projectors.db import Projector, ProjectorDB
+from openlp.core.projectors.editform import ProjectorEditForm
+from openlp.core.projectors.manager import ProjectorManager
from tests.helpers.testmixin import TestMixin
from tests.resources.projector.data import TEST_DB, TEST1_DATA, TEST2_DATA
@@ -48,7 +49,7 @@ class TestProjectorEditForm(TestCase, TestMixin):
self.setup_application()
self.build_settings()
Registry.create()
- with patch('openlp.core.lib.projector.db.init_url') as mocked_init_url:
+ with patch('openlp.core.projectors.db.init_url') as mocked_init_url:
if os.path.exists(TEST_DB):
os.unlink(TEST_DB)
mocked_init_url.return_value = 'sqlite:///' + TEST_DB
@@ -66,7 +67,7 @@ class TestProjectorEditForm(TestCase, TestMixin):
del self.projector_form
self.destroy_settings()
- @patch('openlp.core.ui.projector.editform.QtWidgets.QDialog.exec')
+ @patch('openlp.core.projectors.editform.QtWidgets.QDialog.exec')
def test_edit_form_add_projector(self, mocked_exec):
"""
Test projector edit form with no parameters creates a new entry.
@@ -84,7 +85,7 @@ class TestProjectorEditForm(TestCase, TestMixin):
self.assertTrue((item.ip is None and item.name is None),
'Projector edit form should have a new Projector() instance to edit')
- @patch('openlp.core.ui.projector.editform.QtWidgets.QDialog.exec')
+ @patch('openlp.core.projectors.editform.QtWidgets.QDialog.exec')
def test_edit_form_edit_projector(self, mocked_exec):
"""
Test projector edit form with existing projector entry
diff --git a/tests/interfaces/openlp_core/ui/test_projectormanager.py b/tests/interfaces/openlp_core/ui/test_projectormanager.py
index ff95c4276..484d4d68a 100644
--- a/tests/interfaces/openlp_core/ui/test_projectormanager.py
+++ b/tests/interfaces/openlp_core/ui/test_projectormanager.py
@@ -27,8 +27,9 @@ from unittest import TestCase
from unittest.mock import patch, MagicMock
from openlp.core.common.registry import Registry
-from openlp.core.ui import ProjectorManager, ProjectorEditForm
-from openlp.core.lib.projector.db import ProjectorDB
+from openlp.core.projectors.db import ProjectorDB
+from openlp.core.projectors.editform import ProjectorEditForm
+from openlp.core.projectors.manager import ProjectorManager
from tests.helpers.testmixin import TestMixin
from tests.resources.projector.data import TEST_DB
@@ -42,10 +43,10 @@ class TestProjectorManager(TestCase, TestMixin):
"""
Create the UI and setup necessary options
"""
- self.build_settings()
self.setup_application()
+ self.build_settings()
Registry.create()
- with patch('openlp.core.lib.projector.db.init_url') as mocked_init_url:
+ with patch('openlp.core.projectors.db.init_url') as mocked_init_url:
if os.path.exists(TEST_DB):
os.unlink(TEST_DB)
mocked_init_url.return_value = 'sqlite:///%s' % TEST_DB
diff --git a/tests/interfaces/openlp_core/ui/test_projectorsourceform.py b/tests/interfaces/openlp_core/ui/test_projectorsourceform.py
index 4b9e2f402..815fe6ded 100644
--- a/tests/interfaces/openlp_core/ui/test_projectorsourceform.py
+++ b/tests/interfaces/openlp_core/ui/test_projectorsourceform.py
@@ -32,9 +32,9 @@ from unittest.mock import patch
from PyQt5.QtWidgets import QDialog
from openlp.core.common.registry import Registry
-from openlp.core.lib.projector.db import ProjectorDB, Projector
-from openlp.core.lib.projector.constants import PJLINK_DEFAULT_CODES, PJLINK_DEFAULT_SOURCES
-from openlp.core.ui.projector.sourceselectform import source_group, SourceSelectSingle
+from openlp.core.projectors.db import ProjectorDB, Projector
+from openlp.core.projectors.constants import PJLINK_DEFAULT_CODES, PJLINK_DEFAULT_SOURCES
+from openlp.core.projectors.sourceselectform import source_group, SourceSelectSingle
from tests.helpers.testmixin import TestMixin
from tests.resources.projector.data import TEST_DB, TEST1_DATA
@@ -58,14 +58,14 @@ class ProjectorSourceFormTest(TestCase, TestMixin):
"""
Test class for the Projector Source Select form module
"""
- @patch('openlp.core.lib.projector.db.init_url')
+ @patch('openlp.core.projectors.db.init_url')
def setUp(self, mocked_init_url):
"""
Set up anything necessary for all tests
"""
mocked_init_url.return_value = 'sqlite:///{}'.format(TEST_DB)
- self.build_settings()
self.setup_application()
+ self.build_settings()
Registry.create()
# Do not try to recreate if we've already been created from a previous test
if not hasattr(self, 'projectordb'):
diff --git a/tests/interfaces/openlp_core/ui/test_thememanager.py b/tests/interfaces/openlp_core/ui/test_thememanager.py
index 7f3927cf5..0808b12d0 100644
--- a/tests/interfaces/openlp_core/ui/test_thememanager.py
+++ b/tests/interfaces/openlp_core/ui/test_thememanager.py
@@ -41,8 +41,8 @@ class TestThemeManager(TestCase, TestMixin):
"""
Create the UI
"""
- self.build_settings()
self.setup_application()
+ self.build_settings()
Registry.create()
self.theme_manager = ThemeManager()
diff --git a/tests/utils/__init__.py b/tests/utils/__init__.py
index fd5aeccfd..dd4d78354 100644
--- a/tests/utils/__init__.py
+++ b/tests/utils/__init__.py
@@ -36,7 +36,7 @@ def convert_file_service_item(test_path, name, row=0):
try:
items = json.load(open_file)
first_line = items[row]
- except IOError:
+ except OSError:
first_line = ''
finally:
open_file.close()
diff --git a/tests/utils/test_pylint.py b/tests/utils/test_pylint.py
index 128c0741b..50ca64db6 100644
--- a/tests/utils/test_pylint.py
+++ b/tests/utils/test_pylint.py
@@ -58,17 +58,21 @@ class TestPylint(TestCase):
# GIVEN: Some checks to disable and enable, and the pylint script
disabled_checks = 'import-error,no-member'
enabled_checks = 'missing-format-argument-key,unused-format-string-argument,bad-format-string'
- if is_win() or 'arch' in platform.dist()[0].lower():
- pylint_script = 'pylint'
- else:
- pylint_script = 'pylint3'
+ pylint_kwargs = {
+ 'return_std': True
+ }
+ if version < '1.7.0':
+ if is_win() or 'arch' in platform.dist()[0].lower():
+ pylint_kwargs.update({'script': 'pylint'})
+ else:
+ pylint_kwargs.update({'script': 'pylint3'})
# WHEN: Running pylint
(pylint_stdout, pylint_stderr) = \
lint.py_run('openlp --errors-only --disable={disabled} --enable={enabled} '
'--reports=no --output-format=parseable'.format(disabled=disabled_checks,
enabled=enabled_checks),
- return_std=True, script=pylint_script)
+ **pylint_kwargs)
stdout = pylint_stdout.read()
stderr = pylint_stderr.read()
filtered_stdout = self._filter_tolerated_errors(stdout)