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
Test

data

tokeep
\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'
Test

data

tokeep
' \ @@ -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)