From 5c4ae0a9e64e981fd64c2008f416ba6b7250c7e6 Mon Sep 17 00:00:00 2001 From: Bastian Germann Date: Wed, 3 Oct 2018 00:32:11 +0200 Subject: [PATCH 01/33] replace nose with nose2 --- scripts/check_dependencies.py | 8 +------- tests/README.txt | 14 +++++++------- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/scripts/check_dependencies.py b/scripts/check_dependencies.py index 242b28477..83ab4cc52 100755 --- a/scripts/check_dependencies.py +++ b/scripts/check_dependencies.py @@ -33,12 +33,6 @@ import os import sys from distutils.version import LooseVersion -# If we try to import uno before nose this will create a warning. Just try to import nose first to suppress the warning. -try: - import nose -except ImportError: - nose = None - IS_WIN = sys.platform.startswith('win') IS_LIN = sys.platform.startswith('lin') IS_MAC = sys.platform.startswith('dar') @@ -106,7 +100,7 @@ MODULES = [ OPTIONAL_MODULES = [ ('mysql.connector', '(MySQL support)', True), ('psycopg2', '(PostgreSQL support)', True), - ('nose', '(testing framework)', True), + ('nose2', '(testing framework)', True), ('mock', '(testing module)', sys.version_info[1] < 3), ('jenkins', '(access jenkins api - package name: jenkins-webapi)', True), ('pysword', '(import SWORD bibles)', True), diff --git a/tests/README.txt b/tests/README.txt index 493b5ccb5..926e49043 100644 --- a/tests/README.txt +++ b/tests/README.txt @@ -8,10 +8,10 @@ Prerequisites In order to run the unit tests, you will need the following Python packages/libraries installed: - - Mock - - Nose + - nose2 + - pylint -On Ubuntu you can simple install the python-mock and python-nose packages. Most other distributions will also have these +On Ubuntu you can simple install the python-nose2 and pylint packages. Most other distributions will also have these packages. On Windows and Mac OS X you will need to use ``pip`` or ``easy_install`` to install these packages. Running the Tests @@ -19,16 +19,16 @@ Running the Tests To run the tests, navigate to the root directory of the OpenLP project, and then run the following command:: - nosetests -v tests + nose2 -v tests Or, to run only the functional tests, run the following command:: - nosetests -v tests/functional + nose2 -v tests.functional Or, to run only a particular test suite within a file, run the following command:: - nosetests -v tests/functional/test_applocation.py + nose2 -v tests.functional.openlp_core.test_app Finally, to only run a particular test, run the following command:: - nosetests -v tests/functional/test_applocation.py:TestAppLocation.get_frozen_path_test + nose2 -v tests.functional.openlp_core.test_app.TestOpenLP.test_process_events From 0b1ba4ba6d51149cec78dbbdf2904315c48867f2 Mon Sep 17 00:00:00 2001 From: Bastian Germann Date: Wed, 3 Oct 2018 01:19:49 +0200 Subject: [PATCH 02/33] Fix setup's requirements Move the startup script so that its name does not conflict with the openlp namespace. Codify scripts/check_dependencies.py in setup.py. The name on PyPI is used to declare the dependencies. This is a first step to enable OpenLP distribution via PyPI. The differences are: * pyenchant and pyodbc are declared optional because they are optional in the code and pyenchant is not maintained anymore. * pyenchant's required version is set to 1.6 not only for windows. This version is quite old. * The 5.0 version checks for PyQt5 are left out because this is the first version anyway. * LibreOffice's uno does not exist on PyPI * sqlite3, asyncio and mock are available in Python >= 3.4 anyway and not noted as dependencies. * six is not defined as dependency because the code should be py3 only. The situation with regards to platform wheels being published looks quite promising. As Linux users typically install via their package manager wheel availability is not as import for them as for Win or Mac users. Both of them are available for most dependencies with native extensions. The few exceptions: * PyICU does not publish any platform wheels. More info: https://github.com/ovalhub/pyicu/issues/79 * mysql-connector-python does not publish win32 wheels. * pyenchant does not publish win64 wheels. The wheels are typically available for Py=2.7 and Py>=3.4, although some (mysql-connector-python, PyQt5, pywin32) need Py>=3.5 --- openlp.py => openlp/__main__.py | 5 +++- setup.py | 53 ++++++++++++++++++++++++++------- 2 files changed, 46 insertions(+), 12 deletions(-) rename openlp.py => openlp/__main__.py (99%) diff --git a/openlp.py b/openlp/__main__.py similarity index 99% rename from openlp.py rename to openlp/__main__.py index 38e270a9d..bf6378ed6 100755 --- a/openlp.py +++ b/openlp/__main__.py @@ -42,7 +42,7 @@ def set_up_fault_handling(): faulthandler.enable((AppLocation.get_directory(AppLocation.CacheDir) / 'error.log').open('wb')) -if __name__ == '__main__': +def start(): """ Instantiate and run the application. """ @@ -58,3 +58,6 @@ if __name__ == '__main__': if is_macosx(): sys.argv = [x for x in sys.argv if not x.startswith('-psn')] main() + +if __name__ == '__main__': + start() diff --git a/setup.py b/setup.py index a28fa0300..a7bc15c13 100755 --- a/setup.py +++ b/setup.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python3 # -*- coding: utf-8 -*- # vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 @@ -21,6 +22,7 @@ ############################################################################### import re +import sys from setuptools import setup, find_packages from subprocess import Popen, PIPE @@ -109,6 +111,32 @@ except Exception: finally: ver_file.close() +requires = [ + 'alembic', + 'beautifulsoup4', + 'chardet', + 'lxml', + 'Mako', + 'PyQt5', + 'QtAwesome', + 'requests', + 'SQLAlchemy >= 0.5', + 'waitress', + 'WebOb', + 'websockets' +] +if sys.platform.startswith('win'): + requires.extend([ + 'PyICU', + 'pywin32' + ]) +elif sys.platform.startswith('darwin'): + requires.extend([ + 'pyobjc', + 'pyobjc-framework-Cocoa' + ]) +elif sys.platform.startswith('linux'): + requires.append('dbus-python') setup( name='OpenLP', @@ -156,18 +184,21 @@ using a computer and a data projector.""", keywords='open source church presentation lyrics projection song bible display project', author='Raoul Snyman', author_email='raoulsnyman@openlp.org', - url='http://openlp.org/', + url='https://openlp.org/', license='GNU General Public License', - packages=find_packages(exclude=['ez_setup', 'examples', 'tests']), - scripts=['openlp.py'], + packages=find_packages(exclude=['ez_setup', 'tests*']), include_package_data=True, zip_safe=False, - install_requires=[ - # -*- Extra requirements: -*- - 'sqlalchemy', - 'alembic' - ], - entry_points=""" - # -*- Entry points: -*- - """ + python_requires='>=3.4', + install_requires=requires, + extras_require={ + 'jenkins': ['python-jenkins'], + 'mysql': ['mysql-connector-python'], + 'odbc': ['pyodbc'], + 'postgresql': ['psycopg2'], + 'spellcheck': ['pyenchant >= 1.6'], + 'sword-bibles': ['pysword'] + }, + tests_require=['nose2', 'pylint'], + entry_points={'gui_scripts': ['openlp = openlp.__main__:start']} ) From 41bd1d9bb867d70e8c78bcbd65b0ccdd47f56c5d Mon Sep 17 00:00:00 2001 From: Bastian Germann Date: Fri, 5 Oct 2018 09:24:47 +0200 Subject: [PATCH 03/33] Raise minimum Python version to 3.6 --- scripts/check_dependencies.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/check_dependencies.py b/scripts/check_dependencies.py index 83ab4cc52..9c30742cb 100755 --- a/scripts/check_dependencies.py +++ b/scripts/check_dependencies.py @@ -39,7 +39,7 @@ IS_MAC = sys.platform.startswith('dar') VERS = { - 'Python': '3.4', + 'Python': '3.6', 'PyQt5': '5.0', 'Qt5': '5.0', 'sqlalchemy': '0.5', diff --git a/setup.py b/setup.py index a7bc15c13..4dbb4bd02 100755 --- a/setup.py +++ b/setup.py @@ -189,7 +189,7 @@ using a computer and a data projector.""", packages=find_packages(exclude=['ez_setup', 'tests*']), include_package_data=True, zip_safe=False, - python_requires='>=3.4', + python_requires='>=3.6', install_requires=requires, extras_require={ 'jenkins': ['python-jenkins'], From 1574ecd4f3f9e6b38384b8056a284b4d70039f9a Mon Sep 17 00:00:00 2001 From: Bastian Germann Date: Fri, 5 Oct 2018 09:26:18 +0200 Subject: [PATCH 04/33] Add the optional pyxdg module --- setup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 4dbb4bd02..f154f73bd 100755 --- a/setup.py +++ b/setup.py @@ -197,7 +197,8 @@ using a computer and a data projector.""", 'odbc': ['pyodbc'], 'postgresql': ['psycopg2'], 'spellcheck': ['pyenchant >= 1.6'], - 'sword-bibles': ['pysword'] + 'sword-bibles': ['pysword'], + 'xdg': ['pyxdg'] }, tests_require=['nose2', 'pylint'], entry_points={'gui_scripts': ['openlp = openlp.__main__:start']} From 6edd85146da74faecbbd62240fbd915f18c80619 Mon Sep 17 00:00:00 2001 From: Bastian Germann Date: Sat, 6 Oct 2018 21:04:09 +0200 Subject: [PATCH 05/33] test_get_locale_key needs PyICU --- setup.py | 2 +- tests/functional/openlp_core/common/test_i18n.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index f154f73bd..67768bf88 100755 --- a/setup.py +++ b/setup.py @@ -200,6 +200,6 @@ using a computer and a data projector.""", 'sword-bibles': ['pysword'], 'xdg': ['pyxdg'] }, - tests_require=['nose2', 'pylint'], + tests_require=['nose2', 'PyICU', 'pylint'], entry_points={'gui_scripts': ['openlp = openlp.__main__:start']} ) diff --git a/tests/functional/openlp_core/common/test_i18n.py b/tests/functional/openlp_core/common/test_i18n.py index 7dadcb976..ba7998258 100644 --- a/tests/functional/openlp_core/common/test_i18n.py +++ b/tests/functional/openlp_core/common/test_i18n.py @@ -113,11 +113,11 @@ def test_get_language_invalid_with_none(): assert language is None -@skipIf(is_macosx(), 'This test doesn\'t work on macOS currently') def test_get_locale_key(): """ Test the get_locale_key(string) function """ + import icu with patch('openlp.core.common.i18n.LanguageManager.get_language') as mocked_get_language: # GIVEN: The language is German # 0x00C3 (A with diaresis) should be sorted as "A". 0x00DF (sharp s) should be sorted as "ss". From 9f6b03047bcb7abd952f70f13b0872bc689e2a6c Mon Sep 17 00:00:00 2001 From: Bastian Germann Date: Sat, 6 Oct 2018 22:49:13 +0200 Subject: [PATCH 06/33] Support running ./setup.py test setuptools can run tests. Use nose2 to run the tests. If it is not installed on your system, it is downloaded and temporarily used. --- openlp/core/common/registry.py | 3 +-- setup.py | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/openlp/core/common/registry.py b/openlp/core/common/registry.py index 0dab36bad..1a7cf6cfd 100644 --- a/openlp/core/common/registry.py +++ b/openlp/core/common/registry.py @@ -57,8 +57,7 @@ class Registry(object): registry.functions_list = {} registry.working_flags = {} # Allow the tests to remove Registry entries but not the live system - registry.running_under_test = 'nose' in sys.argv[0] - registry.running_under_test = 'pytest' in sys.argv[0] + registry.running_under_test = 'nose' in sys.argv[0] or 'pytest' in sys.argv[0] registry.initialising = True return registry diff --git a/setup.py b/setup.py index 67768bf88..96675fab8 100755 --- a/setup.py +++ b/setup.py @@ -201,5 +201,6 @@ using a computer and a data projector.""", 'xdg': ['pyxdg'] }, tests_require=['nose2', 'PyICU', 'pylint'], + test_suite='nose2.collector.collector', entry_points={'gui_scripts': ['openlp = openlp.__main__:start']} ) From 7fb3ad4e9e4cb3116adcfc230399c70ba7a67514 Mon Sep 17 00:00:00 2001 From: Bastian Germann Date: Sun, 7 Oct 2018 00:44:01 +0200 Subject: [PATCH 07/33] Reimport pymediainfo with its correct license The mediainfoWrapper.py file was taken from https://github.com/sbraz/pymediainfo/blob/a24c4b79c5/pymediainfo/__init__.py and slightly modified. It is licensed undre MIT but the license information was not copied. Instead it was relicensed as GPLv2 with OpenLP's default copyright. This is the original version. --- .../core/ui/media/vendor/mediainfoWrapper.py | 87 +++++++++++-------- 1 file changed, 52 insertions(+), 35 deletions(-) diff --git a/openlp/core/ui/media/vendor/mediainfoWrapper.py b/openlp/core/ui/media/vendor/mediainfoWrapper.py index d28fe1395..59aadcbd3 100644 --- a/openlp/core/ui/media/vendor/mediainfoWrapper.py +++ b/openlp/core/ui/media/vendor/mediainfoWrapper.py @@ -1,34 +1,37 @@ -# -*- coding: utf-8 -*- -# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 +# The MIT License +# +# Copyright (c) 2010-2014, Patrick Altman +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# http://www.opensource.org/licenses/mit-license.php -############################################################################### -# OpenLP - Open Source Lyrics Projection # -# --------------------------------------------------------------------------- # -# Copyright (c) 2008-2018 OpenLP Developers # -# --------------------------------------------------------------------------- # -# This program is free software; you can redistribute it and/or modify it # -# under the terms of the GNU General Public License as published by the Free # -# Software Foundation; version 2 of the License. # -# # -# This program is distributed in the hope that it will be useful, but WITHOUT # -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # -# more details. # -# # -# You should have received a copy of the GNU General Public License along # -# with this program; if not, write to the Free Software Foundation, Inc., 59 # -# Temple Place, Suite 330, Boston, MA 02111-1307 USA # -############################################################################### -""" -The :mod:`~openlp.core.ui.media.mediainfo` module contains code to run mediainfo on a media file and obtain -information related to the rwquested media. -""" import json import os -from subprocess import check_output +from subprocess import Popen +from tempfile import mkstemp +import six from bs4 import BeautifulSoup, NavigableString +__version__ = '1.4.1' + ENV_DICT = os.environ @@ -74,23 +77,27 @@ class Track(object): pass def __repr__(self): - return "".format(self.track_id, self.track_type) + return("".format(self.track_id, self.track_type)) def to_data(self): data = {} - for k, v in self.__dict__.items(): + for k, v in six.iteritems(self.__dict__): if k != 'xml_dom_fragment': data[k] = v return data -class MediaInfoWrapper(object): +class MediaInfo(object): def __init__(self, xml): self.xml_dom = xml - xml_types = (str,) # no unicode type in python3 + if six.PY3: + xml_types = (str,) # no unicode type in python3 + else: + xml_types = (str, unicode) + if isinstance(xml, xml_types): - self.xml_dom = MediaInfoWrapper.parse_xml_data_into_dom(xml) + self.xml_dom = MediaInfo.parse_xml_data_into_dom(xml) @staticmethod def parse_xml_data_into_dom(xml_data): @@ -98,11 +105,21 @@ class MediaInfoWrapper(object): @staticmethod def parse(filename, environment=ENV_DICT): - xml = check_output(['mediainfo', '-f', '--Output=XML', '--Inform=OLDXML', filename]) - if not xml.startswith(b' Date: Sun, 7 Oct 2018 01:00:18 +0200 Subject: [PATCH 08/33] Reapply OpenLP's changes to pymediainfo --- .../core/ui/media/vendor/mediainfoWrapper.py | 48 ++++++++----------- 1 file changed, 19 insertions(+), 29 deletions(-) diff --git a/openlp/core/ui/media/vendor/mediainfoWrapper.py b/openlp/core/ui/media/vendor/mediainfoWrapper.py index 59aadcbd3..4556d8f25 100644 --- a/openlp/core/ui/media/vendor/mediainfoWrapper.py +++ b/openlp/core/ui/media/vendor/mediainfoWrapper.py @@ -1,6 +1,10 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 + # The MIT License # # Copyright (c) 2010-2014, Patrick Altman +# Copyright (c) 2016-2018, OpenLP Developers # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -22,16 +26,16 @@ # # http://www.opensource.org/licenses/mit-license.php +""" +The :mod:`~openlp.core.ui.media.mediainfo` module contains code to run mediainfo +on a media file and obtain information related to the requested 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 -__version__ = '1.4.1' - ENV_DICT = os.environ @@ -77,27 +81,23 @@ class Track(object): pass def __repr__(self): - return("".format(self.track_id, self.track_type)) + return "".format(self.track_id, self.track_type) 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 -class MediaInfo(object): +class MediaInfoWrapper(object): def __init__(self, xml): self.xml_dom = xml - if six.PY3: - xml_types = (str,) # no unicode type in python3 - else: - xml_types = (str, unicode) - + xml_types = (str,) # no unicode type in python3 if isinstance(xml, xml_types): - self.xml_dom = MediaInfo.parse_xml_data_into_dom(xml) + self.xml_dom = MediaInfoWrapper.parse_xml_data_into_dom(xml) @staticmethod def parse_xml_data_into_dom(xml_data): @@ -105,21 +105,11 @@ class MediaInfo(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 = MediaInfo.parse_xml_data_into_dom(fp_out.read()) - fp_out.close() - fp_err.close() - os.unlink(fname_out) - os.unlink(fname_err) - return MediaInfo(xml_dom) + xml = check_output(['mediainfo', '-f', '--Output=XML', '--Inform=OLDXML', filename]) + if not xml.startswith(b' Date: Sun, 7 Oct 2018 02:24:29 +0200 Subject: [PATCH 09/33] Use upstream pymediainfo Current pymediainfo versions load a DLL/so. Use a very thin wrapper to make sure that the executable is used if the DLL is not available. --- .../core/ui/media/vendor/mediainfoWrapper.py | 150 ++++-------------- scripts/check_dependencies.py | 1 + setup.py | 1 + 3 files changed, 29 insertions(+), 123 deletions(-) diff --git a/openlp/core/ui/media/vendor/mediainfoWrapper.py b/openlp/core/ui/media/vendor/mediainfoWrapper.py index 4556d8f25..91b188369 100644 --- a/openlp/core/ui/media/vendor/mediainfoWrapper.py +++ b/openlp/core/ui/media/vendor/mediainfoWrapper.py @@ -1,135 +1,39 @@ # -*- coding: utf-8 -*- # vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 -# The MIT License -# -# Copyright (c) 2010-2014, Patrick Altman -# Copyright (c) 2016-2018, OpenLP Developers -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -# -# http://www.opensource.org/licenses/mit-license.php - +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2018 OpenLP Developers # +# --------------------------------------------------------------------------- # +# This program is free software; you can redistribute it and/or modify it # +# under the terms of the GNU General Public License as published by the Free # +# Software Foundation; version 2 of the License. # +# # +# This program is distributed in the hope that it will be useful, but WITHOUT # +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # +# more details. # +# # +# You should have received a copy of the GNU General Public License along # +# with this program; if not, write to the Free Software Foundation, Inc., 59 # +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # +############################################################################### """ The :mod:`~openlp.core.ui.media.mediainfo` module contains code to run mediainfo on a media file and obtain information related to the requested media. """ -import json -import os +from pymediainfo import MediaInfo from subprocess import check_output -from bs4 import BeautifulSoup, NavigableString - -ENV_DICT = os.environ - - -class Track(object): - - def __getattribute__(self, name): - try: - return object.__getattribute__(self, name) - except: - pass - return None - - def __init__(self, xml_dom_fragment): - self.xml_dom_fragment = xml_dom_fragment - self.track_type = xml_dom_fragment.attrs['type'] - for el in self.xml_dom_fragment.children: - if not isinstance(el, NavigableString): - node_name = el.name.lower().strip().strip('_') - if node_name == 'id': - node_name = 'track_id' - node_value = el.string - other_node_name = "other_%s" % node_name - if getattr(self, node_name) is None: - setattr(self, node_name, node_value) - else: - if getattr(self, other_node_name) is None: - setattr(self, other_node_name, [node_value, ]) - else: - getattr(self, other_node_name).append(node_value) - - for o in [d for d in self.__dict__.keys() if d.startswith('other_')]: - try: - primary = o.replace('other_', '') - setattr(self, primary, int(getattr(self, primary))) - except: - for v in getattr(self, o): - try: - current = getattr(self, primary) - setattr(self, primary, int(v)) - getattr(self, o).append(current) - break - except: - pass - - def __repr__(self): - return "".format(self.track_id, self.track_type) - - def to_data(self): - data = {} - for k, v in self.__dict__.items(): - if k != 'xml_dom_fragment': - data[k] = v - return data - - class MediaInfoWrapper(object): - def __init__(self, xml): - self.xml_dom = xml - xml_types = (str,) # no unicode type in python3 - if isinstance(xml, xml_types): - self.xml_dom = MediaInfoWrapper.parse_xml_data_into_dom(xml) - @staticmethod - def parse_xml_data_into_dom(xml_data): - return BeautifulSoup(xml_data, "xml") - - @staticmethod - def parse(filename, environment=ENV_DICT): - xml = check_output(['mediainfo', '-f', '--Output=XML', '--Inform=OLDXML', filename]) - if not xml.startswith(b'= 2.1.6', 'PyQt5', 'QtAwesome', 'requests', From ce6cb838daeb9a9b294b55c27504facad0aae969 Mon Sep 17 00:00:00 2001 From: Bastian Germann Date: Sun, 7 Oct 2018 18:53:10 +0200 Subject: [PATCH 10/33] pymediainfo's version 2.2 supports new XML --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 1c1367b76..7965cff7f 100755 --- a/setup.py +++ b/setup.py @@ -117,7 +117,7 @@ requires = [ 'chardet', 'lxml', 'Mako', - 'pymediainfo >= 2.1.6', + 'pymediainfo >= 2.2', 'PyQt5', 'QtAwesome', 'requests', From 79e7ea487f3d734a774f193130b9b78e865d5a5c Mon Sep 17 00:00:00 2001 From: Bastian Germann Date: Sun, 7 Oct 2018 23:40:36 +0200 Subject: [PATCH 11/33] Correct the help message --- openlp/core/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openlp/core/app.py b/openlp/core/app.py index 53e5c06d4..12b5db6b7 100644 --- a/openlp/core/app.py +++ b/openlp/core/app.py @@ -288,7 +288,7 @@ def parse_options(args=None): :return: a tuple of parsed options of type optparse.Value and a list of remaining argsZ """ # Set up command line options. - parser = argparse.ArgumentParser(prog='openlp.py') + parser = argparse.ArgumentParser(prog='openlp') parser.add_argument('-e', '--no-error-form', dest='no_error_form', action='store_true', help='Disable the error notification form.') parser.add_argument('-l', '--log-level', dest='loglevel', default='warning', metavar='LEVEL', From bd3015babff9f56500584421e3a3e577d1903506 Mon Sep 17 00:00:00 2001 From: Bastian Germann Date: Mon, 8 Oct 2018 00:05:04 +0200 Subject: [PATCH 12/33] Media plugin checks for libmediainfo --- openlp/plugins/media/mediaplugin.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openlp/plugins/media/mediaplugin.py b/openlp/plugins/media/mediaplugin.py index 1b3130705..094ea4bec 100644 --- a/openlp/plugins/media/mediaplugin.py +++ b/openlp/plugins/media/mediaplugin.py @@ -25,6 +25,7 @@ The Media plugin import logging import re +from pymediainfo import MediaInfo from PyQt5 import QtCore from openlp.core.api.http import register_endpoint @@ -77,8 +78,8 @@ class MediaPlugin(Plugin): :return: true or false """ log.debug('check_installed Mediainfo') - # Try to find mediainfo in the path - exists = process_check_binary(Path('mediainfo')) + # Try to find libmediainfo or mediainfo in the path + exists = MediaInfo.can_parse() or 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(AppLocation.get_directory(AppLocation.AppDir) / 'mediainfo') From 5f23d3adeaa5d5d31bac2b9a205bd228a42f1f0d Mon Sep 17 00:00:00 2001 From: Bastian Germann Date: Mon, 8 Oct 2018 01:34:00 +0200 Subject: [PATCH 13/33] Use appdirs instead of pyxdg appdirs is used for all systems except Windows. It has Windows support, so it could be used there as well. --- openlp/core/common/applocation.py | 29 +++++++++++++---------------- scripts/check_dependencies.py | 1 + setup.py | 4 ++-- 3 files changed, 16 insertions(+), 18 deletions(-) diff --git a/openlp/core/common/applocation.py b/openlp/core/common/applocation.py index 14e6e4577..7b7c5781d 100644 --- a/openlp/core/common/applocation.py +++ b/openlp/core/common/applocation.py @@ -26,18 +26,13 @@ import logging import os import sys +import appdirs + import openlp from openlp.core.common import get_frozen_path, is_win, is_macosx from openlp.core.common.path import Path, create_paths from openlp.core.common.settings import Settings -if not is_win() and not is_macosx(): - try: - from xdg import BaseDirectory - XDG_BASE_AVAILABLE = True - except ImportError: - XDG_BASE_AVAILABLE = False - log = logging.getLogger(__name__) FROZEN_APP_PATH = Path(sys.argv[0]).parent @@ -143,8 +138,10 @@ def _get_os_dir_path(dir_type): elif dir_type == AppLocation.LanguageDir: return Path(openlp.__file__).parent return openlp_folder_path - elif is_macosx(): - openlp_folder_path = Path(os.getenv('HOME'), 'Library', 'Application Support', 'openlp') + + dirs = appdirs.AppDirs('openlp', multipath=True) + if is_macosx(): + openlp_folder_path = Path(dirs.user_data_dir) if dir_type == AppLocation.DataDir: return openlp_folder_path / 'Data' elif dir_type == AppLocation.LanguageDir: @@ -152,15 +149,15 @@ def _get_os_dir_path(dir_type): return openlp_folder_path else: if dir_type == AppLocation.LanguageDir: - directory = Path('/usr', 'local', 'share', 'openlp') + site_dirs = dirs.site_data_dir.split(os.pathsep) + directory = Path(site_dirs[0]) if directory.exists(): return directory - return Path('/usr', 'share', 'openlp') - if XDG_BASE_AVAILABLE: - if dir_type == AppLocation.DataDir: - return Path(BaseDirectory.xdg_data_home, 'openlp') - elif dir_type == AppLocation.CacheDir: - return Path(BaseDirectory.xdg_cache_home, 'openlp') + return Path(site_dirs[1]) + if dir_type == AppLocation.DataDir: + return Path(dirs.user_data_dir) + elif dir_type == AppLocation.CacheDir: + return Path(dirs.user_cache_dir) if dir_type == AppLocation.DataDir: return Path(os.getenv('HOME'), '.openlp', 'data') return Path(os.getenv('HOME'), '.openlp') diff --git a/scripts/check_dependencies.py b/scripts/check_dependencies.py index e85ea6499..4a02e3465 100755 --- a/scripts/check_dependencies.py +++ b/scripts/check_dependencies.py @@ -79,6 +79,7 @@ MODULES = [ 'PyQt5.QtWebKit', 'PyQt5.QtMultimedia', 'pymediainfo', + 'appdirs', 'sqlalchemy', 'alembic', 'sqlite3', diff --git a/setup.py b/setup.py index 7965cff7f..d9192706a 100755 --- a/setup.py +++ b/setup.py @@ -113,6 +113,7 @@ finally: requires = [ 'alembic', + 'appdirs', 'beautifulsoup4', 'chardet', 'lxml', @@ -198,8 +199,7 @@ using a computer and a data projector.""", 'odbc': ['pyodbc'], 'postgresql': ['psycopg2'], 'spellcheck': ['pyenchant >= 1.6'], - 'sword-bibles': ['pysword'], - 'xdg': ['pyxdg'] + 'sword-bibles': ['pysword'] }, tests_require=['nose2', 'PyICU', 'pylint'], test_suite='nose2.collector.collector', From cbb5afa049f056f24ba93546dc221899345f750c Mon Sep 17 00:00:00 2001 From: Bastian Germann Date: Mon, 8 Oct 2018 10:10:02 +0200 Subject: [PATCH 14/33] Unify module checks with package checks --- scripts/check_dependencies.py | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/scripts/check_dependencies.py b/scripts/check_dependencies.py index 4a02e3465..a0eef5623 100755 --- a/scripts/check_dependencies.py +++ b/scripts/check_dependencies.py @@ -42,9 +42,9 @@ VERS = { 'Python': '3.6', 'PyQt5': '5.0', 'Qt5': '5.0', + 'pymediainfo': '2.2', 'sqlalchemy': '0.5', - # pyenchant 1.6 required on Windows - 'enchant': '1.6' if IS_WIN else '1.3' + 'enchant': '1.6' } # pywin32 @@ -52,7 +52,6 @@ WIN32_MODULES = [ 'win32com', 'win32ui', 'pywintypes', - 'pyodbc', 'icu', ] @@ -82,17 +81,12 @@ MODULES = [ 'appdirs', 'sqlalchemy', 'alembic', - 'sqlite3', 'lxml', 'chardet', - 'enchant', 'bs4', 'mako', - 'uno', 'websockets', - 'asyncio', 'waitress', - 'six', 'webob', 'requests', 'qtawesome' @@ -100,12 +94,16 @@ MODULES = [ OPTIONAL_MODULES = [ - ('mysql.connector', '(MySQL support)', True), - ('psycopg2', '(PostgreSQL support)', True), - ('nose2', '(testing framework)', True), - ('mock', '(testing module)', sys.version_info[1] < 3), - ('jenkins', '(access jenkins api - package name: jenkins-webapi)', True), - ('pysword', '(import SWORD bibles)', True), + ('mysql.connector', '(MySQL support)'), + ('pyodbc', '(ODBC support)'), + ('psycopg2', '(PostgreSQL support)'), + ('jenkins', '(access jenkins api - package name: jenkins-webapi)'), + ('enchant', '(spell checker)'), + ('pysword', '(import SWORD bibles)'), + ('uno', '(LibreOffice/OpenOffice support)'), + # development/testing modules + ('nose2', '(testing framework)'), + ('pylint', '(linter)') ] w = sys.stdout.write @@ -234,8 +232,7 @@ def main(): check_module(m) print('Checking for optional modules...') for m in OPTIONAL_MODULES: - if m[2]: - check_module(m[0], text=m[1]) + check_module(m[0], text=m[1]) if IS_WIN: print('Checking for Windows specific modules...') for m in WIN32_MODULES: From 0e61b22a10040d11ac5b7def4555c53a4e1ab7ba Mon Sep 17 00:00:00 2001 From: Bastian Germann Date: Thu, 11 Oct 2018 00:05:02 +0200 Subject: [PATCH 15/33] Add OFL for FontAwesome --- openlp/core/ui/fonts/LICENSE | 94 ++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 openlp/core/ui/fonts/LICENSE diff --git a/openlp/core/ui/fonts/LICENSE b/openlp/core/ui/fonts/LICENSE new file mode 100644 index 000000000..8ccfa91c1 --- /dev/null +++ b/openlp/core/ui/fonts/LICENSE @@ -0,0 +1,94 @@ +Copyright Dave Gandy 2016. All rights reserved +with Reserved Font Name FontAwesome + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. From e124229040fbb66948e309266577ea6fc8d4e7bb Mon Sep 17 00:00:00 2001 From: Bastian Germann Date: Thu, 11 Oct 2018 20:25:53 +0200 Subject: [PATCH 16/33] Try to fix CI --- openlp/core/lib/__init__.py | 2 +- setup.py | 2 +- tests/functional/openlp_core/common/test_i18n.py | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/openlp/core/lib/__init__.py b/openlp/core/lib/__init__.py index 0111c13e5..e15f81ab6 100644 --- a/openlp/core/lib/__init__.py +++ b/openlp/core/lib/__init__.py @@ -609,4 +609,4 @@ def create_separated_list(string_list): last=string_list[-1]) else: list_to_string = '' - return list_to_string \ No newline at end of file + return list_to_string diff --git a/setup.py b/setup.py index d9192706a..8693fb3b8 100755 --- a/setup.py +++ b/setup.py @@ -188,7 +188,7 @@ using a computer and a data projector.""", author_email='raoulsnyman@openlp.org', url='https://openlp.org/', license='GNU General Public License', - packages=find_packages(exclude=['ez_setup', 'tests*']), + packages=find_packages(exclude=['ez_setup', 'tests']), include_package_data=True, zip_safe=False, python_requires='>=3.6', diff --git a/tests/functional/openlp_core/common/test_i18n.py b/tests/functional/openlp_core/common/test_i18n.py index ba7998258..31d67c873 100644 --- a/tests/functional/openlp_core/common/test_i18n.py +++ b/tests/functional/openlp_core/common/test_i18n.py @@ -162,6 +162,7 @@ def test_check_same_instance(): def test_get_language_from_settings(): assert LanguageManager.get_language() == 'en' + def test_get_language_from_settings_returns_unchanged_if_unknown_format(): Settings().setValue('core/language', '(foobar)') assert LanguageManager.get_language() == '(foobar)' From ba01cd35e141e414654b66052d7cb38a9db9befa Mon Sep 17 00:00:00 2001 From: Bastian Germann Date: Sat, 13 Oct 2018 00:11:04 +0200 Subject: [PATCH 17/33] Optionally depend on launchpadlib --- openlp/core/ui/aboutdialog.py | 4 ++-- scripts/check_dependencies.py | 3 ++- setup.py | 9 ++++++--- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/openlp/core/ui/aboutdialog.py b/openlp/core/ui/aboutdialog.py index cc3cd862a..6ad6eeeeb 100644 --- a/openlp/core/ui/aboutdialog.py +++ b/openlp/core/ui/aboutdialog.py @@ -112,8 +112,8 @@ class UiAboutDialog(object): 'Andreas "googol" Preikschat', 'Ken "alisonken1" Roberts', 'Raoul "superfly" Snyman', 'Jonathan "springermac" Springer', 'Philip "Phill" Ridout'] contributors = ['Stuart "sibecker" Becker', 'Gerald "jerryb" Britton', 'Jonathan "gushie" Corwin', - 'Samuel "MrGamgee" Findlay', 'Michael "cocooncrash" Gorven', 'Scott "sguerrieri" Guerrieri', - 'Simon Hanna', 'Chris Hill', + 'Samuel "MrGamgee" Findlay', 'Bastian Germann', 'Michael "cocooncrash" Gorven', + 'Scott "sguerrieri" Guerrieri', 'Simon Hanna', 'Chris Hill', 'Matthias "matthub" Hub', 'Meinert "m2j" Jordan', 'Ian Knightly' 'Armin "orangeshirt" K\xf6hler', 'Rafael "rafaellerm" Lerm', 'Gabriel loo', 'Erik "luen" Lundin', 'Edwin "edwinlunando" Lunando', diff --git a/scripts/check_dependencies.py b/scripts/check_dependencies.py index a0eef5623..3dfb341d8 100755 --- a/scripts/check_dependencies.py +++ b/scripts/check_dependencies.py @@ -97,11 +97,12 @@ OPTIONAL_MODULES = [ ('mysql.connector', '(MySQL support)'), ('pyodbc', '(ODBC support)'), ('psycopg2', '(PostgreSQL support)'), - ('jenkins', '(access jenkins api - package name: jenkins-webapi)'), ('enchant', '(spell checker)'), ('pysword', '(import SWORD bibles)'), ('uno', '(LibreOffice/OpenOffice support)'), # development/testing modules + ('jenkins', '(access jenkins api)'), + ('launchpadlib', '(launchpad script support)'), ('nose2', '(testing framework)'), ('pylint', '(linter)') ] diff --git a/setup.py b/setup.py index 8693fb3b8..1fe60169c 100755 --- a/setup.py +++ b/setup.py @@ -138,6 +138,7 @@ elif sys.platform.startswith('darwin'): 'pyobjc-framework-Cocoa' ]) elif sys.platform.startswith('linux'): + # dbus-python could be replaced by PyQt5.QtDBus requires.append('dbus-python') setup( @@ -194,14 +195,16 @@ using a computer and a data projector.""", python_requires='>=3.6', install_requires=requires, extras_require={ - 'jenkins': ['python-jenkins'], 'mysql': ['mysql-connector-python'], 'odbc': ['pyodbc'], 'postgresql': ['psycopg2'], 'spellcheck': ['pyenchant >= 1.6'], - 'sword-bibles': ['pysword'] + 'sword-bibles': ['pysword'], + # Required for scripts/*.py: + 'jenkins': ['python-jenkins'], + 'launchpad': ['launchpadlib'] }, - tests_require=['nose2', 'PyICU', 'pylint'], + tests_require=['nose2', 'PyICU', 'pylint', 'pyodbc', 'pysword'], test_suite='nose2.collector.collector', entry_points={'gui_scripts': ['openlp = openlp.__main__:start']} ) From 035bd08a9a3ef5c581dd28b1e00e85cda9cfe404 Mon Sep 17 00:00:00 2001 From: Bastian Germann Date: Sat, 13 Oct 2018 11:12:00 +0200 Subject: [PATCH 18/33] pylint3 --- tests/README.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/README.txt b/tests/README.txt index 926e49043..edf788d4a 100644 --- a/tests/README.txt +++ b/tests/README.txt @@ -9,9 +9,9 @@ Prerequisites In order to run the unit tests, you will need the following Python packages/libraries installed: - nose2 - - pylint + - pylint3 -On Ubuntu you can simple install the python-nose2 and pylint packages. Most other distributions will also have these +On Ubuntu you can simple install the python-nose2 and pylint3 packages. Most other distributions will also have these packages. On Windows and Mac OS X you will need to use ``pip`` or ``easy_install`` to install these packages. Running the Tests From f0fb9e773d758bf583374df210c942aa5d2c1f39 Mon Sep 17 00:00:00 2001 From: Bastian Germann Date: Tue, 16 Oct 2018 09:09:18 +0200 Subject: [PATCH 19/33] no mandatory icu for tests --- setup.py | 1 - tests/functional/openlp_core/common/test_i18n.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 1fe60169c..2ee755251 100755 --- a/setup.py +++ b/setup.py @@ -138,7 +138,6 @@ elif sys.platform.startswith('darwin'): 'pyobjc-framework-Cocoa' ]) elif sys.platform.startswith('linux'): - # dbus-python could be replaced by PyQt5.QtDBus requires.append('dbus-python') setup( diff --git a/tests/functional/openlp_core/common/test_i18n.py b/tests/functional/openlp_core/common/test_i18n.py index 31d67c873..a4b896c6b 100644 --- a/tests/functional/openlp_core/common/test_i18n.py +++ b/tests/functional/openlp_core/common/test_i18n.py @@ -113,11 +113,11 @@ def test_get_language_invalid_with_none(): assert language is None +@skipIf(is_macosx(), 'This test doesn\'t work on macOS currently') def test_get_locale_key(): """ Test the get_locale_key(string) function """ - import icu with patch('openlp.core.common.i18n.LanguageManager.get_language') as mocked_get_language: # GIVEN: The language is German # 0x00C3 (A with diaresis) should be sorted as "A". 0x00DF (sharp s) should be sorted as "ss". From b5e717a0bd2afcb1c2ed0f95ac5f0d602ab41cf8 Mon Sep 17 00:00:00 2001 From: Bastian Germann Date: Tue, 16 Oct 2018 09:18:48 +0200 Subject: [PATCH 20/33] Make Code-Analysis happy --- openlp/core/ui/media/vendor/mediainfoWrapper.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openlp/core/ui/media/vendor/mediainfoWrapper.py b/openlp/core/ui/media/vendor/mediainfoWrapper.py index 91b188369..feb6ec7df 100644 --- a/openlp/core/ui/media/vendor/mediainfoWrapper.py +++ b/openlp/core/ui/media/vendor/mediainfoWrapper.py @@ -26,6 +26,7 @@ on a media file and obtain information related to the requested media. from pymediainfo import MediaInfo from subprocess import check_output + class MediaInfoWrapper(object): @staticmethod From 7eafbac68d71a882944a02c462fe1bb21187f5f7 Mon Sep 17 00:00:00 2001 From: Bastian Germann Date: Tue, 16 Oct 2018 09:29:58 +0200 Subject: [PATCH 21/33] Make Code-Analysis happy --- openlp/__main__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openlp/__main__.py b/openlp/__main__.py index bf6378ed6..89f89db61 100755 --- a/openlp/__main__.py +++ b/openlp/__main__.py @@ -59,5 +59,6 @@ def start(): sys.argv = [x for x in sys.argv if not x.startswith('-psn')] main() + if __name__ == '__main__': start() From 62636c5b27528202d70f0e3994e91e179b267feb Mon Sep 17 00:00:00 2001 From: Bastian Germann Date: Tue, 16 Oct 2018 09:39:42 +0200 Subject: [PATCH 22/33] Update dependencies for appveyor tests --- scripts/appveyor.yml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/scripts/appveyor.yml b/scripts/appveyor.yml index 5948253b9..bef8cb5e5 100644 --- a/scripts/appveyor.yml +++ b/scripts/appveyor.yml @@ -11,11 +11,8 @@ environment: install: # Install dependencies from pypi - - "%PYTHON%\\python.exe -m pip install sqlalchemy alembic chardet beautifulsoup4 Mako nose mock pyodbc==4.0.8 psycopg2 pypiwin32==219 pyenchant websockets asyncio waitress six webob requests QtAwesome" - # Install mysql dependency - - "%PYTHON%\\python.exe -m pip install http://cdn.mysql.com/Downloads/Connector-Python/mysql-connector-python-2.0.4.zip#md5=3df394d89300db95163f17c843ef49df" - # Download and install lxml and pyicu (originally from http://www.lfd.uci.edu/~gohlke/pythonlibs/) - - "%PYTHON%\\python.exe -m pip install https://get.openlp.org/win-sdk/lxml-3.6.4-cp34-cp34m-win32.whl" + - "%PYTHON%\\python.exe -m pip install sqlalchemy alembic appdirs chardet beautifulsoup4 lxml Mako mysql-connector-python nose mock pyodbc==4.0.8 psycopg2 pypiwin32==219 pyenchant pymediainfo websockets asyncio waitress six webob requests QtAwesome" + # Download and install pyicu (originally from http://www.lfd.uci.edu/~gohlke/pythonlibs/) - "%PYTHON%\\python.exe -m pip install https://get.openlp.org/win-sdk/PyICU-1.9.5-cp34-cp34m-win32.whl" # Download and install PyQt5 - appveyor DownloadFile http://downloads.sourceforge.net/project/pyqt/PyQt5/PyQt-5.5.1/PyQt5-5.5.1-gpl-Py3.4-Qt5.5.1-x32.exe From ddc66fcd2f37b38a158831e7adc672ece0c35d4c Mon Sep 17 00:00:00 2001 From: Bastian Germann Date: Tue, 16 Oct 2018 20:32:30 +0200 Subject: [PATCH 23/33] Try to fix macOS tests --- openlp/core/ui/media/vendor/mediainfoWrapper.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openlp/core/ui/media/vendor/mediainfoWrapper.py b/openlp/core/ui/media/vendor/mediainfoWrapper.py index feb6ec7df..995317c88 100644 --- a/openlp/core/ui/media/vendor/mediainfoWrapper.py +++ b/openlp/core/ui/media/vendor/mediainfoWrapper.py @@ -25,13 +25,15 @@ on a media file and obtain information related to the requested media. """ from pymediainfo import MediaInfo from subprocess import check_output +import sys class MediaInfoWrapper(object): @staticmethod def parse(filename): - if MediaInfo.can_parse(): + # FIXME: library loading on macOS + if sys.platform != 'darwin' and MediaInfo.can_parse(): return MediaInfo.parse(filename) else: xml = check_output(['mediainfo', '-f', '--Output=XML', '--Inform=OLDXML', filename]) From dd346487f560a34f8492a7cdf2c65b09c8625c6b Mon Sep 17 00:00:00 2001 From: Bastian Germann Date: Tue, 16 Oct 2018 20:55:25 +0200 Subject: [PATCH 24/33] Find macOS problem --- openlp/core/ui/media/vendor/mediainfoWrapper.py | 3 +-- .../openlp_core/ui/media/vendor/test_mediainfoWrapper.py | 2 ++ 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/openlp/core/ui/media/vendor/mediainfoWrapper.py b/openlp/core/ui/media/vendor/mediainfoWrapper.py index 995317c88..58115515f 100644 --- a/openlp/core/ui/media/vendor/mediainfoWrapper.py +++ b/openlp/core/ui/media/vendor/mediainfoWrapper.py @@ -32,8 +32,7 @@ class MediaInfoWrapper(object): @staticmethod def parse(filename): - # FIXME: library loading on macOS - if sys.platform != 'darwin' and MediaInfo.can_parse(): + if MediaInfo.can_parse(): return MediaInfo.parse(filename) else: xml = check_output(['mediainfo', '-f', '--Output=XML', '--Inform=OLDXML', filename]) 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 1d106e1d4..87ba20875 100644 --- a/tests/interfaces/openlp_core/ui/media/vendor/test_mediainfoWrapper.py +++ b/tests/interfaces/openlp_core/ui/media/vendor/test_mediainfoWrapper.py @@ -44,5 +44,7 @@ class TestMediainfoWrapper(TestCase): # WHEN the media data is retrieved results = MediaInfoWrapper.parse(full_path) + assert ['fail'] == results.tracks + # THEN you can determine the run time assert results.tracks[0].duration == test_data[1], 'The correct duration is returned for ' + test_data[0] From 2e39304a1bcaafe859f141cc6c555d9cfbb5dc67 Mon Sep 17 00:00:00 2001 From: Bastian Germann Date: Tue, 16 Oct 2018 20:58:51 +0200 Subject: [PATCH 25/33] Find macOS problem --- .../openlp_core/ui/media/vendor/test_mediainfoWrapper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 87ba20875..5e769de63 100644 --- a/tests/interfaces/openlp_core/ui/media/vendor/test_mediainfoWrapper.py +++ b/tests/interfaces/openlp_core/ui/media/vendor/test_mediainfoWrapper.py @@ -44,7 +44,7 @@ class TestMediainfoWrapper(TestCase): # WHEN the media data is retrieved results = MediaInfoWrapper.parse(full_path) - assert ['fail'] == results.tracks + assert 'fail' == results # THEN you can determine the run time assert results.tracks[0].duration == test_data[1], 'The correct duration is returned for ' + test_data[0] From 45a1c3d3f15b2bc7ba8bc5fe258b56b8585cb412 Mon Sep 17 00:00:00 2001 From: Bastian Germann Date: Tue, 16 Oct 2018 21:07:31 +0200 Subject: [PATCH 26/33] Find macOS problem --- openlp/core/ui/media/vendor/mediainfoWrapper.py | 2 +- .../openlp_core/ui/media/vendor/test_mediainfoWrapper.py | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/openlp/core/ui/media/vendor/mediainfoWrapper.py b/openlp/core/ui/media/vendor/mediainfoWrapper.py index 58115515f..9348b1b5a 100644 --- a/openlp/core/ui/media/vendor/mediainfoWrapper.py +++ b/openlp/core/ui/media/vendor/mediainfoWrapper.py @@ -25,7 +25,6 @@ on a media file and obtain information related to the requested media. """ from pymediainfo import MediaInfo from subprocess import check_output -import sys class MediaInfoWrapper(object): @@ -38,4 +37,5 @@ class MediaInfoWrapper(object): xml = check_output(['mediainfo', '-f', '--Output=XML', '--Inform=OLDXML', filename]) if not xml.startswith(b' Date: Tue, 16 Oct 2018 21:24:47 +0200 Subject: [PATCH 27/33] Decode byte string for MediaInfo --- openlp/core/ui/media/vendor/mediainfoWrapper.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openlp/core/ui/media/vendor/mediainfoWrapper.py b/openlp/core/ui/media/vendor/mediainfoWrapper.py index 9348b1b5a..d3f95cbff 100644 --- a/openlp/core/ui/media/vendor/mediainfoWrapper.py +++ b/openlp/core/ui/media/vendor/mediainfoWrapper.py @@ -37,5 +37,4 @@ class MediaInfoWrapper(object): xml = check_output(['mediainfo', '-f', '--Output=XML', '--Inform=OLDXML', filename]) if not xml.startswith(b' Date: Tue, 16 Oct 2018 21:57:43 +0200 Subject: [PATCH 28/33] Document pytest as a test runner --- tests/README.txt | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/tests/README.txt b/tests/README.txt index edf788d4a..865d22d1b 100644 --- a/tests/README.txt +++ b/tests/README.txt @@ -8,10 +8,10 @@ Prerequisites In order to run the unit tests, you will need the following Python packages/libraries installed: - - nose2 + - pytest - pylint3 -On Ubuntu you can simple install the python-nose2 and pylint3 packages. Most other distributions will also have these +On Ubuntu you can simple install the python3-pytest and pylint3 packages. Most other distributions will also have these packages. On Windows and Mac OS X you will need to use ``pip`` or ``easy_install`` to install these packages. Running the Tests @@ -19,16 +19,12 @@ Running the Tests To run the tests, navigate to the root directory of the OpenLP project, and then run the following command:: - nose2 -v tests + pytest -v tests Or, to run only the functional tests, run the following command:: - nose2 -v tests.functional + pytest -v tests/functional Or, to run only a particular test suite within a file, run the following command:: - nose2 -v tests.functional.openlp_core.test_app - -Finally, to only run a particular test, run the following command:: - - nose2 -v tests.functional.openlp_core.test_app.TestOpenLP.test_process_events + pytest -v tests/functional/openlp_core/test_app.py From 0484d0bf23dbcfccbec4c05803f20eaa929691d9 Mon Sep 17 00:00:00 2001 From: Bastian Germann Date: Tue, 16 Oct 2018 22:07:00 +0200 Subject: [PATCH 29/33] Move the openlp.__main__ module to run_openlp --- openlp/__main__.py => run_openlp.py | 0 setup.py | 3 ++- 2 files changed, 2 insertions(+), 1 deletion(-) rename openlp/__main__.py => run_openlp.py (100%) diff --git a/openlp/__main__.py b/run_openlp.py similarity index 100% rename from openlp/__main__.py rename to run_openlp.py diff --git a/setup.py b/setup.py index 2ee755251..bdd4c6e94 100755 --- a/setup.py +++ b/setup.py @@ -189,6 +189,7 @@ using a computer and a data projector.""", url='https://openlp.org/', license='GNU General Public License', packages=find_packages(exclude=['ez_setup', 'tests']), + py_modules=['run_openlp'], include_package_data=True, zip_safe=False, python_requires='>=3.6', @@ -205,5 +206,5 @@ using a computer and a data projector.""", }, tests_require=['nose2', 'PyICU', 'pylint', 'pyodbc', 'pysword'], test_suite='nose2.collector.collector', - entry_points={'gui_scripts': ['openlp = openlp.__main__:start']} + entry_points={'gui_scripts': ['openlp = run_openlp:start']} ) From e458622fccbc49211ff3fe40d7fc56fd8917d867 Mon Sep 17 00:00:00 2001 From: Bastian Germann Date: Tue, 16 Oct 2018 22:58:39 +0200 Subject: [PATCH 30/33] Revert the mediainfo upstreaming Keep the pymediainfo dependency for later --- .../core/ui/media/vendor/mediainfoWrapper.py | 110 ++++++++++++++++-- openlp/plugins/media/mediaplugin.py | 5 +- 2 files changed, 101 insertions(+), 14 deletions(-) diff --git a/openlp/core/ui/media/vendor/mediainfoWrapper.py b/openlp/core/ui/media/vendor/mediainfoWrapper.py index d3f95cbff..d28fe1395 100644 --- a/openlp/core/ui/media/vendor/mediainfoWrapper.py +++ b/openlp/core/ui/media/vendor/mediainfoWrapper.py @@ -20,21 +20,109 @@ # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### """ -The :mod:`~openlp.core.ui.media.mediainfo` module contains code to run mediainfo -on a media file and obtain information related to the requested media. +The :mod:`~openlp.core.ui.media.mediainfo` module contains code to run mediainfo on a media file and obtain +information related to the rwquested media. """ -from pymediainfo import MediaInfo +import json +import os from subprocess import check_output +from bs4 import BeautifulSoup, NavigableString + +ENV_DICT = os.environ + + +class Track(object): + + def __getattribute__(self, name): + try: + return object.__getattribute__(self, name) + except: + pass + return None + + def __init__(self, xml_dom_fragment): + self.xml_dom_fragment = xml_dom_fragment + self.track_type = xml_dom_fragment.attrs['type'] + for el in self.xml_dom_fragment.children: + if not isinstance(el, NavigableString): + node_name = el.name.lower().strip().strip('_') + if node_name == 'id': + node_name = 'track_id' + node_value = el.string + other_node_name = "other_%s" % node_name + if getattr(self, node_name) is None: + setattr(self, node_name, node_value) + else: + if getattr(self, other_node_name) is None: + setattr(self, other_node_name, [node_value, ]) + else: + getattr(self, other_node_name).append(node_value) + + for o in [d for d in self.__dict__.keys() if d.startswith('other_')]: + try: + primary = o.replace('other_', '') + setattr(self, primary, int(getattr(self, primary))) + except: + for v in getattr(self, o): + try: + current = getattr(self, primary) + setattr(self, primary, int(v)) + getattr(self, o).append(current) + break + except: + pass + + def __repr__(self): + return "".format(self.track_id, self.track_type) + + def to_data(self): + data = {} + for k, v in self.__dict__.items(): + if k != 'xml_dom_fragment': + data[k] = v + return data + class MediaInfoWrapper(object): + def __init__(self, xml): + self.xml_dom = xml + xml_types = (str,) # no unicode type in python3 + if isinstance(xml, xml_types): + self.xml_dom = MediaInfoWrapper.parse_xml_data_into_dom(xml) + @staticmethod - def parse(filename): - if MediaInfo.can_parse(): - return MediaInfo.parse(filename) - else: - xml = check_output(['mediainfo', '-f', '--Output=XML', '--Inform=OLDXML', filename]) - if not xml.startswith(b' Date: Fri, 19 Oct 2018 21:33:32 -0700 Subject: [PATCH 31/33] PJLink2 Update T and pep8 --- openlp/core/common/__init__.py | 8 +- openlp/core/common/settings.py | 3 +- openlp/core/projectors/editform.py | 1 + openlp/core/projectors/manager.py | 81 +++++-- openlp/core/projectors/pjlink.py | 58 ++++- openlp/core/projectors/tab.py | 47 ++++ .../common/test_network_interfaces.py | 78 ------ .../common/test_network_interfaces.py | 224 ++++++++++++++++++ tests/openlp_core/projectors/__init__.py | 4 +- .../projectors/test_projector_pjlink_udp.py | 195 ++++++++++++++- 10 files changed, 566 insertions(+), 133 deletions(-) delete mode 100644 tests/functional/openlp_core/common/test_network_interfaces.py create mode 100644 tests/openlp_core/common/test_network_interfaces.py diff --git a/openlp/core/common/__init__.py b/openlp/core/common/__init__.py index 0148bce99..a7215e394 100644 --- a/openlp/core/common/__init__.py +++ b/openlp/core/common/__init__.py @@ -60,7 +60,6 @@ def get_local_ip4(): :returns: Dict of interfaces """ - # Get the local IPv4 active address(es) that are NOT localhost (lo or '127.0.0.1') log.debug('Getting local IPv4 interface(es) information') my_ip4 = {} for iface in QNetworkInterface.allInterfaces(): @@ -70,8 +69,6 @@ def get_local_ip4(): log.debug('Checking address(es) protocol') for address in iface.addressEntries(): ip = address.ip() - # NOTE: Next line will skip if interface is localhost - keep for now until we decide about it later - # if (ip.protocol() == QAbstractSocket.IPv4Protocol) and (ip != QHostAddress.LocalHost): log.debug('Checking for protocol == IPv4Protocol') if ip.protocol() == QAbstractSocket.IPv4Protocol: log.debug('Getting interface information') @@ -83,12 +80,13 @@ def get_local_ip4(): ip.toIPv4Address()).toString() } log.debug('Adding {iface} to active list'.format(iface=iface.name())) + if len(my_ip4) == 0: + log.warning('No active IPv4 network interfaces detected') + return my_ip4 if 'localhost' in my_ip4: log.debug('Renaming windows localhost to lo') my_ip4['lo'] = my_ip4['localhost'] my_ip4.pop('localhost') - if len(my_ip4) == 0: - log.warning('No active IPv4 network interfaces detected') if len(my_ip4) == 1: if 'lo' in my_ip4: # No active interfaces - so leave localhost in there diff --git a/openlp/core/common/settings.py b/openlp/core/common/settings.py index 74d686b09..8ac41375c 100644 --- a/openlp/core/common/settings.py +++ b/openlp/core/common/settings.py @@ -217,7 +217,8 @@ class Settings(QtCore.QSettings): 'projector/last directory export': None, 'projector/poll time': 20, # PJLink timeout is 30 seconds 'projector/socket timeout': 5, # 5 second socket timeout - 'projector/source dialog type': 0 # Source select dialog box type + 'projector/source dialog type': 0, # Source select dialog box type + 'projector/udp broadcast listen': False # Enable/disable listening for PJLink 2 UDP broadcast packets } __file_path__ = '' # Settings upgrades prior to 3.0 diff --git a/openlp/core/projectors/editform.py b/openlp/core/projectors/editform.py index ef88605ef..04ad668ca 100644 --- a/openlp/core/projectors/editform.py +++ b/openlp/core/projectors/editform.py @@ -179,6 +179,7 @@ class ProjectorEditForm(QtWidgets.QDialog, Ui_ProjectorEditForm): Validate input before accepting input. """ log.debug('accept_me() signal received') + valid = True if len(self.name_text.text().strip()) < 1: QtWidgets.QMessageBox.warning(self, translate('OpenLP.ProjectorEdit', 'Name Not Set'), diff --git a/openlp/core/projectors/manager.py b/openlp/core/projectors/manager.py index c2575c60a..add7cd4c0 100644 --- a/openlp/core/projectors/manager.py +++ b/openlp/core/projectors/manager.py @@ -32,13 +32,13 @@ from PyQt5 import QtCore, QtGui, QtWidgets from openlp.core.common.i18n import translate from openlp.core.ui.icons import UiIcons from openlp.core.common.mixins import LogMixin, RegistryProperties -from openlp.core.common.registry import RegistryBase +from openlp.core.common.registry import Registry, RegistryBase from openlp.core.common.settings import Settings from openlp.core.lib.ui import create_widget_action from openlp.core.projectors import DialogSourceStyle from openlp.core.projectors.constants import E_AUTHENTICATION, E_ERROR, E_NETWORK, E_NOT_CONNECTED, \ E_SOCKET_TIMEOUT, E_UNKNOWN_SOCKET_ERROR, S_CONNECTED, S_CONNECTING, S_COOLDOWN, S_INITIALIZE, \ - S_NOT_CONNECTED, S_OFF, S_ON, S_STANDBY, S_WARMUP, PJLINK_PORT, STATUS_CODE, STATUS_MSG, QSOCKET_STATE + S_NOT_CONNECTED, S_OFF, S_ON, S_STANDBY, S_WARMUP, STATUS_CODE, STATUS_MSG, QSOCKET_STATE from openlp.core.projectors.db import ProjectorDB from openlp.core.projectors.editform import ProjectorEditForm @@ -297,7 +297,7 @@ class ProjectorManager(QtWidgets.QWidget, RegistryBase, UiProjectorManager, LogM self.projector_list = [] self.source_select_form = None # Dictionary of PJLinkUDP objects to listen for UDP broadcasts from PJLink 2+ projectors. - # Key is port number that projectors use + # Key is port number self.pjlink_udp = {} # Dict for matching projector status to display icon self.status_icons = { @@ -335,10 +335,6 @@ class ProjectorManager(QtWidgets.QWidget, RegistryBase, UiProjectorManager, LogM """ Post-initialize setups. """ - # Default PJLink port UDP socket - log.debug('Creating PJLinkUDP listener for default port {port}'.format(port=PJLINK_PORT)) - self.pjlink_udp = {PJLINK_PORT: PJLinkUDP(port=PJLINK_PORT)} - self.pjlink_udp[PJLINK_PORT].bind(PJLINK_PORT) # Set 1.5 second delay before loading all projectors if self.autostart: log.debug('Delaying 1.5 seconds before loading all projectors') @@ -351,6 +347,36 @@ class ProjectorManager(QtWidgets.QWidget, RegistryBase, UiProjectorManager, LogM self.projector_form.editProjector.connect(self.edit_projector_from_wizard) self.projector_list_widget.itemSelectionChanged.connect(self.update_icons) + def udp_listen_add(self, port): + """ + Add UDP broadcast listener + """ + if port in self.pjlink_udp: + log.warning('UDP Listener for port {port} already added - skipping'.format(port=port)) + else: + log.debug('Adding UDP listener on port {port}'.format(port=port)) + self.pjlink_udp[port] = PJLinkUDP(port=port) + Registry().execute('udp_broadcast_add', port=port, callback=self.pjlink_udp[port].check_settings) + + def udp_listen_delete(self, port): + """ + Remove a UDP broadcast listener + """ + log.debug('Checking for UDP port {port} listener deletion'.format(port=port)) + if port not in self.pjlink_udp: + log.warn('UDP listener for port {port} not there - skipping delete'.format(port=port)) + return + keep_port = False + for item in self.projector_list: + if port == item.link.port: + keep_port = True + if keep_port: + log.warn('UDP listener for port {port} needed for other projectors - skipping delete'.format(port=port)) + return + Registry().execute('udp_broadcast_remove', port=port) + del self.pjlink_udp[port] + log.debug('UDP listener for port {port} deleted'.format(port=port)) + def get_settings(self): """ Retrieve the saved settings @@ -518,25 +544,22 @@ class ProjectorManager(QtWidgets.QWidget, RegistryBase, UiProjectorManager, LogM except (AttributeError, TypeError): pass try: - projector.poll_timer.stop() - projector.poll_timer.timeout.disconnect(projector.link.poll_loop) + projector.link.poll_timer.stop() + projector.link.poll_timer.timeout.disconnect(projector.link.poll_loop) except (AttributeError, TypeError): pass try: - projector.socket_timer.stop() - projector.socket_timer.timeout.disconnect(projector.link.socket_abort) - except (AttributeError, TypeError): - pass - # Disconnect signals from projector being deleted - try: - self.pjlink_udp[projector.link.port].data_received.disconnect(projector.link.get_buffer) + projector.link.socket_timer.stop() + projector.link.socket_timer.timeout.disconnect(projector.link.socket_abort) except (AttributeError, TypeError): pass + old_port = projector.link.port # Rebuild projector list new_list = [] for item in self.projector_list: if item.link.db_item.id == projector.link.db_item.id: + log.debug('Removing projector "{item}"'.format(item=item.link.name)) continue new_list.append(item) self.projector_list = new_list @@ -546,6 +569,7 @@ class ProjectorManager(QtWidgets.QWidget, RegistryBase, UiProjectorManager, LogM log.warning('Delete projector {item} failed'.format(item=projector.db_item)) for item in self.projector_list: log.debug('New projector list - item: {ip} {name}'.format(ip=item.link.ip, name=item.link.name)) + self.udp_listen_delete(old_port) def on_disconnect_projector(self, opt=None): """ @@ -748,15 +772,8 @@ class ProjectorManager(QtWidgets.QWidget, RegistryBase, UiProjectorManager, LogM item.link.projectorAuthentication.connect(self.authentication_error) item.link.projectorNoAuthentication.connect(self.no_authentication_error) item.link.projectorUpdateIcons.connect(self.update_icons) - # Connect UDP signal to projector instances with same port - if item.link.port not in self.pjlink_udp: - log.debug('Adding new PJLinkUDP listener fo port {port}'.format(port=item.link.port)) - self.pjlink_udp[item.link.port] = PJLinkUDP(port=item.link.port) - self.pjlink_udp[item.link.port].bind(item.link.port) - log.debug('Connecting PJLinkUDP port {port} signal to "{item}"'.format(port=item.link.port, - item=item.link.name)) - self.pjlink_udp[item.link.port].data_received.connect(item.link.get_buffer) - + # Add UDP listener for new projector port + self.udp_listen_add(item.link.port) self.projector_list.append(item) if start: item.link.connect_to_host() @@ -783,13 +800,25 @@ class ProjectorManager(QtWidgets.QWidget, RegistryBase, UiProjectorManager, LogM :param projector: Projector() instance of projector with updated information """ log.debug('edit_projector_from_wizard(ip={ip})'.format(ip=projector.ip)) + old_port = self.old_projector.link.port + old_ip = self.old_projector.link.ip self.old_projector.link.name = projector.name self.old_projector.link.ip = projector.ip self.old_projector.link.pin = None if projector.pin == '' else projector.pin - self.old_projector.link.port = projector.port self.old_projector.link.location = projector.location self.old_projector.link.notes = projector.notes self.old_projector.widget.setText(projector.name) + self.old_projector.link.port = int(projector.port) + # Update projector list items + for item in self.projector_list: + if item.link.ip == old_ip: + item.link.port = int(projector.port) + # NOTE: This assumes (!) we are using IP addresses as keys + break + # Update UDP listeners before setting old_projector.port + if old_port != projector.port: + self.udp_listen_delete(old_port) + self.udp_listen_add(int(projector.port)) def _load_projectors(self): """' diff --git a/openlp/core/projectors/pjlink.py b/openlp/core/projectors/pjlink.py index 3ea1bb414..cd178bb48 100644 --- a/openlp/core/projectors/pjlink.py +++ b/openlp/core/projectors/pjlink.py @@ -98,32 +98,51 @@ class PJLinkUDP(QtNetwork.QUdpSocket): self.search_active = False self.search_time = 30000 # 30 seconds for allowed time self.search_timer = QtCore.QTimer() + self.udp_broadcast_listen_setting = False + log.debug('(UDP:{port}) PJLinkUDP() Initialized'.format(port=self.port)) + if Settings().value('projector/udp broadcast listen'): + self.udp_start() + + def udp_start(self): + """ + Start listening on UDP port + """ + log.debug('(UDP:{port}) Start called'.format(port=self.port)) self.readyRead.connect(self.get_datagram) - log.debug('(UDP) PJLinkUDP() Initialized for port {port}'.format(port=self.port)) + self.check_settings(checked=Settings().value('projector/udp broadcast listen')) + + def udp_stop(self): + """ + Stop listening on UDP port + """ + log.debug('(UDP:{port}) Stopping listener'.format(port=self.port)) + self.close() + self.readyRead.disconnect(self.get_datagram) @QtCore.pyqtSlot() def get_datagram(self): """ Retrieve packet and basic checks """ - log.debug('(UDP) get_datagram() - Receiving data') + log.debug('(UDP:{port}) get_datagram() - Receiving data'.format(port=self.port)) read_size = self.pendingDatagramSize() if -1 == read_size: - log.warning('(UDP) No data (-1)') + log.warning('(UDP:{port}) No data (-1)'.format(port=self.port)) return elif 0 == read_size: - log.warning('(UDP) get_datagram() called when pending data size is 0') + log.warning('(UDP:{port}) get_datagram() called when pending data size is 0'.format(port=self.port)) return elif read_size > PJLINK_MAX_PACKET: - log.warning('(UDP) UDP Packet too large ({size} bytes)- ignoring'.format(size=read_size)) + log.warning('(UDP:{port}) UDP Packet too large ({size} bytes)- ignoring'.format(size=read_size, + port=self.port)) return data_in, peer_host, peer_port = self.readDatagram(read_size) data = data_in.decode('utf-8') if isinstance(data_in, bytes) else data_in - log.debug('(UDP) {size} bytes received from {adx} on port {port}'.format(size=len(data), - adx=peer_host.toString(), - port=self.port)) - log.debug('(UDP) packet "{data}"'.format(data=data)) - log.debug('(UDP) Sending data_received signal to projectors') + log.debug('(UDP:{port}) {size} bytes received from {adx}'.format(size=len(data), + adx=peer_host.toString(), + port=self.port)) + log.debug('(UDP:{port}) packet "{data}"'.format(data=data, port=self.port)) + log.debug('(UDP:{port}) Sending data_received signal to projectors'.format(port=self.port)) self.data_received.emit(peer_host, self.localPort(), data) return @@ -143,6 +162,25 @@ class PJLinkUDP(QtNetwork.QUdpSocket): self.search_active = False self.search_timer.stop() + def check_settings(self, checked): + """ + Update UDP listening state based on settings change. + NOTE: This method is called by projector settings tab and setup/removed by ProjectorManager + """ + if self.udp_broadcast_listen_setting == checked: + log.debug('(UDP:{port}) No change to status - skipping'.format(port=self.port)) + return + self.udp_broadcast_listen_setting = checked + if self.udp_broadcast_listen_setting: + if self.state() == self.ListeningState: + log.debug('(UDP:{port}) Already listening - skipping') + return + self.bind(self.port) + log.debug('(UDP:{port}) Listening'.format(port=self.port)) + else: + # Close socket + self.udp_stop() + class PJLinkCommands(object): """ diff --git a/openlp/core/projectors/tab.py b/openlp/core/projectors/tab.py index 2407bad66..fa76e1c25 100644 --- a/openlp/core/projectors/tab.py +++ b/openlp/core/projectors/tab.py @@ -27,6 +27,7 @@ import logging from PyQt5 import QtWidgets from openlp.core.common.i18n import UiStrings, translate +from openlp.core.common.registry import Registry from openlp.core.common.settings import Settings from openlp.core.lib.settingstab import SettingsTab from openlp.core.ui.icons import UiIcons @@ -47,8 +48,11 @@ class ProjectorTab(SettingsTab): :param parent: Parent widget """ self.icon_path = UiIcons().projector + self.udp_listeners = {} # Key on port number projector_translated = translate('OpenLP.ProjectorTab', 'Projector') super(ProjectorTab, self).__init__(parent, 'Projector', projector_translated) + Registry().register_function('udp_broadcast_add', self.add_udp_listener) + Registry().register_function('udp_broadcast_remove', self.remove_udp_listener) def setupUi(self): """ @@ -90,6 +94,10 @@ class ProjectorTab(SettingsTab): self.connect_box_layout.addRow(self.dialog_type_label, self.dialog_type_combo_box) self.left_layout.addStretch() self.dialog_type_combo_box.activated.connect(self.on_dialog_type_combo_box_changed) + # Enable/disable listening on UDP ports for PJLink2 broadcasts + self.udp_broadcast_listen = QtWidgets.QCheckBox(self.connect_box) + self.udp_broadcast_listen.setObjectName('udp_broadcast_listen') + self.connect_box_layout.addRow(self.udp_broadcast_listen) # Connect on LKUP packet received (PJLink v2+ only) self.connect_on_linkup = QtWidgets.QCheckBox(self.connect_box) self.connect_on_linkup.setObjectName('connect_on_linkup') @@ -116,6 +124,9 @@ class ProjectorTab(SettingsTab): translate('OpenLP.ProjectorTab', 'Single dialog box')) self.connect_on_linkup.setText( translate('OpenLP.ProjectorTab', 'Connect to projector when LINKUP received (v2 only)')) + self.udp_broadcast_listen.setText( + translate('OpenLP.ProjectorTab', 'Enable listening for PJLink2 broadcast messages')) + log.debug('PJLink settings tab initialized') def load(self): """ @@ -125,6 +136,7 @@ class ProjectorTab(SettingsTab): self.socket_timeout_spin_box.setValue(Settings().value('projector/socket timeout')) self.socket_poll_spin_box.setValue(Settings().value('projector/poll time')) self.dialog_type_combo_box.setCurrentIndex(Settings().value('projector/source dialog type')) + self.udp_broadcast_listen.setChecked(Settings().value('projector/udp broadcast listen')) self.connect_on_linkup.setChecked(Settings().value('projector/connect when LKUP received')) def save(self): @@ -136,6 +148,41 @@ class ProjectorTab(SettingsTab): Settings().setValue('projector/poll time', self.socket_poll_spin_box.value()) Settings().setValue('projector/source dialog type', self.dialog_type_combo_box.currentIndex()) Settings().setValue('projector/connect when LKUP received', self.connect_on_linkup.isChecked()) + Settings().setValue('projector/udp broadcast listen', self.udp_broadcast_listen.isChecked()) + self.call_udp_listener() def on_dialog_type_combo_box_changed(self): self.dialog_type = self.dialog_type_combo_box.currentIndex() + + def add_udp_listener(self, port, callback): + """ + Add new UDP listener to list + """ + if port in self.udp_listeners: + log.warning('Port {port} already in list - not adding'.format(port=port)) + return + self.udp_listeners[port] = callback + log.debug('PJLinkSettings: new callback list: {port}'.format(port=self.udp_listeners.keys())) + + def remove_udp_listener(self, port): + """ + Remove UDP listener from list + """ + if port not in self.udp_listeners: + log.warning('Port {port} not in list - ignoring'.format(port=port)) + return + # Turn off listener before deleting + self.udp_listeners[port](checked=False) + del self.udp_listeners[port] + log.debug('PJLinkSettings: new callback list: {port}'.format(port=self.udp_listeners.keys())) + + def call_udp_listener(self): + """ + Call listeners to update UDP listen setting + """ + if len(self.udp_listeners) < 1: + log.warning('PJLinkSettings: No callers - returning') + return + log.debug('PJLinkSettings: Calling UDP listeners') + for call in self.udp_listeners: + self.udp_listeners[call](checked=self.udp_broadcast_listen.isChecked()) diff --git a/tests/functional/openlp_core/common/test_network_interfaces.py b/tests/functional/openlp_core/common/test_network_interfaces.py deleted file mode 100644 index d1547bd0a..000000000 --- a/tests/functional/openlp_core/common/test_network_interfaces.py +++ /dev/null @@ -1,78 +0,0 @@ -# -*- coding: utf-8 -*- -# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 - -############################################################################### -# OpenLP - Open Source Lyrics Projection # -# --------------------------------------------------------------------------- # -# Copyright (c) 2008-2017 OpenLP Developers # -# --------------------------------------------------------------------------- # -# This program is free software; you can redistribute it and/or modify it # -# under the terms of the GNU General Public License as published by the Free # -# Software Foundation; version 2 of the License. # -# # -# This program is distributed in the hope that it will be useful, but WITHOUT # -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # -# more details. # -# # -# You should have received a copy of the GNU General Public License along # -# with this program; if not, write to the Free Software Foundation, Inc., 59 # -# Temple Place, Suite 330, Boston, MA 02111-1307 USA # -############################################################################### -""" -Functional tests to test calls for network interfaces. -""" -from unittest import TestCase -from unittest.mock import MagicMock, call, patch - -import openlp.core.common -from openlp.core.common import get_local_ip4 - -from tests.helpers.testmixin import TestMixin - -lo_address_attrs = {'isValid.return_value': True, - 'flags.return_value': True, - 'InterfaceFlags.return_value': True, - 'name.return_value': 'lo', - 'broadcast.toString.return_value': '127.0.0.255', - 'netmask.toString.return_value': '255.0.0.0', - 'prefixLength.return_value': 8, - 'ip.protocol.return_value': True} - - -class TestInterfaces(TestCase, TestMixin): - """ - A test suite to test out functions/methods that use network interface(s). - """ - def setUp(self): - """ - Create an instance and a few example actions. - """ - self.build_settings() - - self.ip4_lo_address = MagicMock() - self.ip4_lo_address.configure_mock(**lo_address_attrs) - - def tearDown(self): - """ - Clean up - """ - self.destroy_settings() - - @patch.object(openlp.core.common, 'log') - def test_ip4_no_interfaces(self, mock_log): - """ - Test no interfaces available - """ - # GIVEN: Test environment - call_warning = [call('No active IPv4 network interfaces detected')] - - with patch('openlp.core.common.QNetworkInterface') as mock_newtork_interface: - mock_newtork_interface.allInterfaces.return_value = [] - - # WHEN: get_local_ip4 is called - ifaces = get_local_ip4() - - # THEN: There should not be any interfaces detected - assert not ifaces, 'There should have been no active interfaces' - mock_log.warning.assert_has_calls(call_warning) diff --git a/tests/openlp_core/common/test_network_interfaces.py b/tests/openlp_core/common/test_network_interfaces.py new file mode 100644 index 000000000..7a049b234 --- /dev/null +++ b/tests/openlp_core/common/test_network_interfaces.py @@ -0,0 +1,224 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2017 OpenLP Developers # +# --------------------------------------------------------------------------- # +# This program is free software; you can redistribute it and/or modify it # +# under the terms of the GNU General Public License as published by the Free # +# Software Foundation; version 2 of the License. # +# # +# This program is distributed in the hope that it will be useful, but WITHOUT # +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # +# more details. # +# # +# You should have received a copy of the GNU General Public License along # +# with this program; if not, write to the Free Software Foundation, Inc., 59 # +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # +############################################################################### +""" +Functional tests to test calls for network interfaces. +""" +from unittest import TestCase +from unittest.mock import call, patch +from PyQt5.QtCore import QObject +from PyQt5.QtNetwork import QHostAddress, QNetworkAddressEntry, QNetworkInterface + +import openlp.core.common +from openlp.core.common import get_local_ip4 + +from tests.helpers.testmixin import TestMixin + + +class FakeIP4InterfaceEntry(QObject): + """ + Class to face an interface for testing purposes + """ + def __init__(self, name='lo'): + self.my_name = name + if name in ['localhost', 'lo']: + self.my_ip = QNetworkAddressEntry() + self.my_ip.setBroadcast(QHostAddress('255.0.0.0')) + self.my_ip.setIp(QHostAddress('127.0.0.2')) + self.my_ip.setPrefixLength(8) + self.fake_data = {'lo': {'ip': '127.0.0.2', + 'broadcast': '255.0.0.0', + 'netmask': '255.0.0.0', + 'prefix': 8, + 'localnet': '127.0.0.0'}} + else: + # Define a fake real address + self.my_ip = QNetworkAddressEntry() + self.my_ip.setBroadcast(QHostAddress('255.255.255.0')) + self.my_ip.setIp(QHostAddress('127.254.0.2')) + self.my_ip.setPrefixLength(24) + self.fake_data = {self.my_name: {'ip': '127.254.0.2', + 'broadcast': '255.255.255.0', + 'netmask': '255.255.255.0', + 'prefix': 24, + 'localnet': '127.254.0.0'}} + + def addressEntries(self): + """ + Return fake IP address + """ + return [self.my_ip] + + def flags(self): + """ + Return a QFlags enum with IsUp and IsRunning + """ + return (QNetworkInterface.IsUp | QNetworkInterface.IsRunning) + + def name(self): + return self.my_name + + def isValid(self): + return True + + +class TestInterfaces(TestCase, TestMixin): + """ + A test suite to test out functions/methods that use network interface(s). + """ + def setUp(self): + """ + Create an instance and a few example actions. + """ + self.build_settings() + if not hasattr(self, 'fake_lo'): + # Since these shouldn't change, only need to instantiate them the first time + self.fake_lo = FakeIP4InterfaceEntry() + self.fake_localhost = FakeIP4InterfaceEntry(name='localhost') + self.fake_address = FakeIP4InterfaceEntry(name='eth25') + + def tearDown(self): + """ + Clean up + """ + self.destroy_settings() + + @patch.object(openlp.core.common, 'log') + def test_ip4_no_interfaces(self, mock_log): + """ + Test no interfaces available + """ + # GIVEN: Test environment + call_debug = [call('Getting local IPv4 interface(es) information')] + call_warning = [call('No active IPv4 network interfaces detected')] + + # WHEN: get_local_ip4 is called + with patch('openlp.core.common.QNetworkInterface') as mock_network_interface: + mock_network_interface.allInterfaces.return_value = [] + ifaces = get_local_ip4() + + # THEN: There should not be any interfaces detected + mock_log.debug.assert_has_calls(call_debug) + mock_log.warning.assert_has_calls(call_warning) + assert not ifaces, 'There should have been no active interfaces listed' + + @patch.object(openlp.core.common, 'log') + def test_ip4_lo(self, mock_log): + """ + Test get_local_ip4 returns proper dictionary with 'lo' + """ + # GIVEN: Test environment + call_debug = [call('Getting local IPv4 interface(es) information'), + call('Checking for isValid and flags == IsUP | IsRunning'), + call('Checking address(es) protocol'), + call('Checking for protocol == IPv4Protocol'), + call('Getting interface information'), + call('Adding lo to active list')] + call_warning = [call('No active IPv4 interfaces found except localhost')] + + # WHEN: get_local_ip4 is called + with patch('openlp.core.common.QNetworkInterface') as mock_network_interface: + mock_network_interface.allInterfaces.return_value = [self.fake_lo] + ifaces = get_local_ip4() + + # THEN: There should be a fake 'lo' interface + mock_log.debug.assert_has_calls(call_debug) + mock_log.warning.assert_has_calls(call_warning) + assert ifaces == self.fake_lo.fake_data, "There should have been an 'lo' interface listed" + + @patch.object(openlp.core.common, 'log') + def test_ip4_localhost(self, mock_log): + """ + Test get_local_ip4 returns proper dictionary with 'lo' if interface is 'localhost' + """ + # GIVEN: Test environment + call_debug = [call('Getting local IPv4 interface(es) information'), + call('Checking for isValid and flags == IsUP | IsRunning'), + call('Checking address(es) protocol'), + call('Checking for protocol == IPv4Protocol'), + call('Getting interface information'), + call('Adding localhost to active list'), + call('Renaming windows localhost to lo')] + call_warning = [call('No active IPv4 interfaces found except localhost')] + + # WHEN: get_local_ip4 is called + with patch('openlp.core.common.QNetworkInterface') as mock_network_interface: + mock_network_interface.allInterfaces.return_value = [self.fake_localhost] + ifaces = get_local_ip4() + + # THEN: There should be a fake 'lo' interface + mock_log.debug.assert_has_calls(call_debug) + mock_log.warning.assert_has_calls(call_warning) + assert ifaces == self.fake_lo.fake_data, "There should have been an 'lo' interface listed" + + @patch.object(openlp.core.common, 'log') + def test_ip4_eth25(self, mock_log): + """ + Test get_local_ip4 returns proper dictionary with 'eth25' + """ + # GIVEN: Test environment + call_debug = [call('Getting local IPv4 interface(es) information'), + call('Checking for isValid and flags == IsUP | IsRunning'), + call('Checking address(es) protocol'), + call('Checking for protocol == IPv4Protocol'), + call('Getting interface information'), + call('Adding eth25 to active list')] + call_warning = [] + + # WHEN: get_local_ip4 is called + with patch('openlp.core.common.QNetworkInterface') as mock_network_interface: + mock_network_interface.allInterfaces.return_value = [self.fake_address] + ifaces = get_local_ip4() + + # THEN: There should be a fake 'eth25' interface + mock_log.debug.assert_has_calls(call_debug) + mock_log.warning.assert_has_calls(call_warning) + assert ifaces == self.fake_address.fake_data + + @patch.object(openlp.core.common, 'log') + def test_ip4_lo_eth25(self, mock_log): + """ + Test get_local_ip4 returns proper dictionary with 'eth25' + """ + # GIVEN: Test environment + call_debug = [call('Getting local IPv4 interface(es) information'), + call('Checking for isValid and flags == IsUP | IsRunning'), + call('Checking address(es) protocol'), + call('Checking for protocol == IPv4Protocol'), + call('Getting interface information'), + call('Adding lo to active list'), + call('Checking for isValid and flags == IsUP | IsRunning'), + call('Checking address(es) protocol'), + call('Checking for protocol == IPv4Protocol'), + call('Getting interface information'), + call('Adding eth25 to active list'), + call('Found at least one IPv4 interface, removing localhost')] + call_warning = [] + + # WHEN: get_local_ip4 is called + with patch('openlp.core.common.QNetworkInterface') as mock_network_interface: + mock_network_interface.allInterfaces.return_value = [self.fake_lo, self.fake_address] + ifaces = get_local_ip4() + + # THEN: There should be a fake 'eth25' interface + mock_log.debug.assert_has_calls(call_debug) + mock_log.warning.assert_has_calls(call_warning) + assert ifaces == self.fake_address.fake_data, "There should have been only 'eth25' interface listed" diff --git a/tests/openlp_core/projectors/__init__.py b/tests/openlp_core/projectors/__init__.py index 0a1a5ca7e..64536c41d 100644 --- a/tests/openlp_core/projectors/__init__.py +++ b/tests/openlp_core/projectors/__init__.py @@ -4,7 +4,7 @@ ############################################################################### # OpenLP - Open Source Lyrics Projection # # --------------------------------------------------------------------------- # -# Copyright (c) 2008-2017 OpenLP Developers # +# Copyright (c) 2008-2018 OpenLP Developers # # --------------------------------------------------------------------------- # # This program is free software; you can redistribute it and/or modify it # # under the terms of the GNU General Public License as published by the Free # @@ -20,5 +20,5 @@ # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### """ -Module-level functions for the projector test suite +:mod tests/openlp_core/projectors: Tests for projector code """ diff --git a/tests/openlp_core/projectors/test_projector_pjlink_udp.py b/tests/openlp_core/projectors/test_projector_pjlink_udp.py index 45c91347f..564e13128 100644 --- a/tests/openlp_core/projectors/test_projector_pjlink_udp.py +++ b/tests/openlp_core/projectors/test_projector_pjlink_udp.py @@ -28,16 +28,45 @@ from unittest import TestCase from unittest.mock import call, patch import openlp.core.projectors.pjlink +from openlp.core.common.registry import Registry from openlp.core.projectors.constants import PJLINK_PORT - +from openlp.core.projectors.tab import ProjectorTab from openlp.core.projectors.pjlink import PJLinkUDP + +from tests.helpers.testmixin import TestMixin from tests.resources.projector.data import TEST1_DATA -class TestPJLinkBase(TestCase): +class TestPJLinkBase(TestCase, TestMixin): """ Tests for the PJLinkUDP class """ + def setUp(self): + """ + Create the UI and setup necessary options + """ + self.setup_application() + self.build_settings() + Registry.create() + """ + 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 + self.projectordb = ProjectorDB() + if not hasattr(self, 'projector_manager'): + self.projector_manager = ProjectorManager(projectordb=self.projectordb) + """ + + def tearDown(self): + """ + Remove test database. + Delete all the C++ objects at the end so that we don't have a segfault. + """ + # self.projectordb.session.close() + self.destroy_settings() + # del self.projector_manager + @patch.object(openlp.core.projectors.pjlink, 'log') def test_get_datagram_data_negative_zero_length(self, mock_log): """ @@ -45,9 +74,9 @@ class TestPJLinkBase(TestCase): """ # GIVEN: Test setup pjlink_udp = PJLinkUDP() - log_warning_calls = [call('(UDP) No data (-1)')] - log_debug_calls = [call('(UDP) PJLinkUDP() Initialized for port 4352'), - call('(UDP) get_datagram() - Receiving data')] + log_warning_calls = [call('(UDP:4352) No data (-1)')] + log_debug_calls = [call('(UDP:4352) PJLinkUDP() Initialized'), + call('(UDP:4352) get_datagram() - Receiving data')] with patch.object(pjlink_udp, 'pendingDatagramSize') as mock_datagram, \ patch.object(pjlink_udp, 'readDatagram') as mock_read: mock_datagram.return_value = -1 @@ -67,9 +96,9 @@ class TestPJLinkBase(TestCase): """ # GIVEN: Test setup pjlink_udp = PJLinkUDP() - log_warning_calls = [call('(UDP) get_datagram() called when pending data size is 0')] - log_debug_calls = [call('(UDP) PJLinkUDP() Initialized for port 4352'), - call('(UDP) get_datagram() - Receiving data')] + log_warning_calls = [call('(UDP:4352) get_datagram() called when pending data size is 0')] + log_debug_calls = [call('(UDP:4352) PJLinkUDP() Initialized'), + call('(UDP:4352) get_datagram() - Receiving data')] with patch.object(pjlink_udp, 'pendingDatagramSize') as mock_datagram, \ patch.object(pjlink_udp, 'readDatagram') as mock_read: mock_datagram.return_value = 0 @@ -89,9 +118,9 @@ class TestPJLinkBase(TestCase): """ # GIVEN: Test setup pjlink_udp = PJLinkUDP() - log_warning_calls = [call('(UDP) get_datagram() called when pending data size is 0')] - log_debug_calls = [call('(UDP) PJLinkUDP() Initialized for port 4352'), - call('(UDP) get_datagram() - Receiving data')] + log_warning_calls = [call('(UDP:4352) get_datagram() called when pending data size is 0')] + log_debug_calls = [call('(UDP:4352) PJLinkUDP() Initialized'), + call('(UDP:4352) get_datagram() - Receiving data')] with patch.object(pjlink_udp, 'pendingDatagramSize') as mock_datagram: mock_datagram.return_value = 0 @@ -101,3 +130,147 @@ class TestPJLinkBase(TestCase): # THEN: Log entries should be made and method returns mock_log.warning.assert_has_calls(log_warning_calls) mock_log.debug.assert_has_calls(log_debug_calls) + + @patch.object(openlp.core.projectors.tab, 'log') + def test_pjlinksettings_add_udp_listener(self, mock_log): + """ + Test adding UDP listners to PJLink Settings tab + """ + # GIVEN: Initial setup + log_debug_calls = [call('PJLink settings tab initialized'), + call('PJLinkSettings: new callback list: dict_keys([4352])')] + log_warning_calls = [] + + pjlink_udp = PJLinkUDP() + settings_tab = ProjectorTab(parent=None) + + # WHEN: add_udp_listener is called with single port + settings_tab.add_udp_listener(port=pjlink_udp.port, callback=pjlink_udp.check_settings) + + # THEN: settings tab should have one entry + assert len(settings_tab.udp_listeners) == 1 + assert pjlink_udp.port in settings_tab.udp_listeners + mock_log.debug.assert_has_calls(log_debug_calls) + mock_log.warning.assert_has_calls(log_warning_calls) + + @patch.object(openlp.core.projectors.tab, 'log') + def test_pjlinksettings_add_udp_listener_multiple_same(self, mock_log): + """ + Test adding second UDP listner with same port to PJLink Settings tab + """ + # GIVEN: Initial setup + log_debug_calls = [call('PJLink settings tab initialized'), + call('PJLinkSettings: new callback list: dict_keys([4352])')] + log_warning_calls = [call('Port 4352 already in list - not adding')] + pjlink_udp = PJLinkUDP() + settings_tab = ProjectorTab(parent=None) + settings_tab.add_udp_listener(port=pjlink_udp.port, callback=pjlink_udp.check_settings) + + # WHEN: add_udp_listener is called with second instance same port + settings_tab.add_udp_listener(port=pjlink_udp.port, callback=pjlink_udp.check_settings) + + # THEN: settings tab should have one entry + assert len(settings_tab.udp_listeners) == 1 + assert pjlink_udp.port in settings_tab.udp_listeners + mock_log.debug.assert_has_calls(log_debug_calls) + mock_log.warning.assert_has_calls(log_warning_calls) + + @patch.object(openlp.core.projectors.tab, 'log') + def test_pjlinksettings_add_udp_listener_multiple_different(self, mock_log): + """ + Test adding second UDP listner with different port to PJLink Settings tab + """ + # GIVEN: Initial setup + log_debug_calls = [call('PJLink settings tab initialized'), + call('PJLinkSettings: new callback list: dict_keys([4352])')] + log_warning_calls = [] + + settings_tab = ProjectorTab(parent=None) + pjlink_udp1 = PJLinkUDP(port=4352) + settings_tab.add_udp_listener(port=pjlink_udp1.port, callback=pjlink_udp1.check_settings) + + # WHEN: add_udp_listener is called with second instance different port + pjlink_udp2 = PJLinkUDP(port=4353) + settings_tab.add_udp_listener(port=pjlink_udp2.port, callback=pjlink_udp2.check_settings) + + # THEN: settings tab should have two entry + assert len(settings_tab.udp_listeners) == 2 + assert pjlink_udp1.port in settings_tab.udp_listeners + assert pjlink_udp2.port in settings_tab.udp_listeners + mock_log.debug.assert_has_calls(log_debug_calls) + mock_log.warning.assert_has_calls(log_warning_calls) + + @patch.object(openlp.core.projectors.tab, 'log') + def test_pjlinksettings_remove_udp_listener(self, mock_log): + """ + Test removing UDP listners to PJLink Settings tab + """ + # GIVEN: Initial setup + log_debug_calls = [call('PJLink settings tab initialized'), + call('PJLinkSettings: new callback list: dict_keys([4352])'), + call('PJLinkSettings: new callback list: dict_keys([])')] + log_warning_calls = [] + + pjlink_udp = PJLinkUDP() + settings_tab = ProjectorTab(parent=None) + settings_tab.add_udp_listener(port=pjlink_udp.port, callback=pjlink_udp.check_settings) + + # WHEN: remove_udp_listener is called with single port + settings_tab.remove_udp_listener(port=pjlink_udp.port) + + # THEN: settings tab should have one entry + assert len(settings_tab.udp_listeners) == 0 + mock_log.debug.assert_has_calls(log_debug_calls) + mock_log.warning.assert_has_calls(log_warning_calls) + + @patch.object(openlp.core.projectors.tab, 'log') + def test_pjlinksettings_remove_udp_listener_multiple_different(self, mock_log): + """ + Test adding second UDP listner with different port to PJLink Settings tab + """ + # GIVEN: Initial setup + log_debug_calls = [call('PJLink settings tab initialized'), + call('PJLinkSettings: new callback list: dict_keys([4352])')] + log_warning_calls = [] + + settings_tab = ProjectorTab(parent=None) + pjlink_udp1 = PJLinkUDP(port=4352) + settings_tab.add_udp_listener(port=pjlink_udp1.port, callback=pjlink_udp1.check_settings) + pjlink_udp2 = PJLinkUDP(port=4353) + settings_tab.add_udp_listener(port=pjlink_udp2.port, callback=pjlink_udp2.check_settings) + + # WHEN: remove_udp_listener called for one port + settings_tab.remove_udp_listener(port=4353) + + # THEN: settings tab should have one entry + assert len(settings_tab.udp_listeners) == 1 + assert pjlink_udp1.port in settings_tab.udp_listeners + assert pjlink_udp2.port not in settings_tab.udp_listeners + mock_log.debug.assert_has_calls(log_debug_calls) + mock_log.warning.assert_has_calls(log_warning_calls) + + @patch.object(PJLinkUDP, 'check_settings') + @patch.object(openlp.core.projectors.pjlink, 'log') + @patch.object(openlp.core.projectors.tab, 'log') + def test_pjlinksettings_call_udp_listener(self, mock_tab_log, mock_pjlink_log, mock_check_settings): + """ + Test calling UDP listners in PJLink Settings tab + """ + # GIVEN: Initial setup + tab_debug_calls = [call('PJLink settings tab initialized'), + call('PJLinkSettings: new callback list: dict_keys([4352])'), + call('PJLinkSettings: Calling UDP listeners')] + pjlink_debug_calls = [call.debug('(UDP:4352) PJLinkUDP() Initialized')] + + pjlink_udp = PJLinkUDP() + settings_tab = ProjectorTab(parent=None) + settings_tab.add_udp_listener(port=pjlink_udp.port, callback=pjlink_udp.check_settings) + + # WHEN: calling UDP listener via registry + settings_tab.call_udp_listener() + + # THEN: settings tab should have one entry + assert len(settings_tab.udp_listeners) == 1 + mock_check_settings.assert_called() + mock_tab_log.debug.assert_has_calls(tab_debug_calls) + mock_pjlink_log.assert_has_calls(pjlink_debug_calls) From bdec3f407b4723152595bfdc2484982a010317bc Mon Sep 17 00:00:00 2001 From: Philip Ridout Date: Mon, 22 Oct 2018 21:17:55 +0100 Subject: [PATCH 32/33] Remove proxy settings from individual bibles and use the centeral OpenLP proxy server settings. --- openlp/core/common/settings.py | 9 +- openlp/plugins/bibles/bibleplugin.py | 4 - .../plugins/bibles/forms/bibleimportform.py | 64 +---- openlp/plugins/bibles/lib/db.py | 9 +- openlp/plugins/bibles/lib/importers/http.py | 45 +--- openlp/plugins/bibles/lib/manager.py | 4 - openlp/plugins/bibles/lib/upgrade.py | 55 ++++- .../openlp_plugins/bibles/test_upgrade.py | 219 ++++++++++++++++++ tests/resources/bibles/tests.sqlite | Bin 26624 -> 26624 bytes .../web-bible-2.4.6-proxy-meta-v1.sqlite | Bin 0 -> 49152 bytes .../bibles/web-bible-2.4.6-v1.sqlite | Bin 0 -> 49152 bytes 11 files changed, 308 insertions(+), 101 deletions(-) create mode 100644 tests/functional/openlp_plugins/bibles/test_upgrade.py create mode 100644 tests/resources/bibles/web-bible-2.4.6-proxy-meta-v1.sqlite create mode 100644 tests/resources/bibles/web-bible-2.4.6-v1.sqlite diff --git a/openlp/core/common/settings.py b/openlp/core/common/settings.py index 74d686b09..6fe0429cb 100644 --- a/openlp/core/common/settings.py +++ b/openlp/core/common/settings.py @@ -274,7 +274,14 @@ class Settings(QtCore.QSettings): ('songuasge/db password', 'songusage/db password', []), ('songuasge/db hostname', 'songusage/db hostname', []), ('songuasge/db database', 'songusage/db database', []), - ('presentations / Powerpoint Viewer', '', []) + ('presentations / Powerpoint Viewer', '', []), + ('songuasge/db hostname', 'songusage/db hostname', []), + ('songuasge/db hostname', 'songusage/db hostname', []), + ('songuasge/db hostname', 'songusage/db hostname', []), + ('bibles/proxy name', '', []), # Just remove these bible proxy settings. They weren't used in 2.4! + ('bibles/proxy address', '', []), + ('bibles/proxy username', '', []), + ('bibles/proxy password', '', []) ] @staticmethod diff --git a/openlp/plugins/bibles/bibleplugin.py b/openlp/plugins/bibles/bibleplugin.py index 1b507e97f..85d903546 100644 --- a/openlp/plugins/bibles/bibleplugin.py +++ b/openlp/plugins/bibles/bibleplugin.py @@ -52,10 +52,6 @@ __default_settings__ = { 'bibles/display new chapter': False, 'bibles/second bibles': True, 'bibles/primary bible': '', - 'bibles/proxy name': '', - 'bibles/proxy address': '', - 'bibles/proxy username': '', - 'bibles/proxy password': '', 'bibles/bible theme': '', 'bibles/verse separator': '', 'bibles/range separator': '', diff --git a/openlp/plugins/bibles/forms/bibleimportform.py b/openlp/plugins/bibles/forms/bibleimportform.py index d32711b62..912ab26e5 100644 --- a/openlp/plugins/bibles/forms/bibleimportform.py +++ b/openlp/plugins/bibles/forms/bibleimportform.py @@ -210,22 +210,22 @@ class BibleImportForm(OpenLPWizard): self.open_song_layout.addRow(self.open_song_file_label, self.open_song_path_edit) self.open_song_layout.setItem(1, QtWidgets.QFormLayout.LabelRole, self.spacer) self.select_stack.addWidget(self.open_song_widget) - self.web_tab_widget = QtWidgets.QTabWidget(self.select_page) - self.web_tab_widget.setObjectName('WebTabWidget') + self.web_widget = QtWidgets.QWidget(self.select_page) + self.web_widget.setObjectName('WebWidget') self.web_bible_tab = QtWidgets.QWidget() self.web_bible_tab.setObjectName('WebBibleTab') - self.web_bible_layout = QtWidgets.QFormLayout(self.web_bible_tab) + self.web_bible_layout = QtWidgets.QFormLayout(self.web_widget) self.web_bible_layout.setObjectName('WebBibleLayout') - self.web_update_label = QtWidgets.QLabel(self.web_bible_tab) + self.web_update_label = QtWidgets.QLabel(self.web_widget) self.web_update_label.setObjectName('WebUpdateLabel') self.web_bible_layout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.web_update_label) - self.web_update_button = QtWidgets.QPushButton(self.web_bible_tab) + self.web_update_button = QtWidgets.QPushButton(self.web_widget) self.web_update_button.setObjectName('WebUpdateButton') self.web_bible_layout.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.web_update_button) - self.web_source_label = QtWidgets.QLabel(self.web_bible_tab) + self.web_source_label = QtWidgets.QLabel(self.web_widget) self.web_source_label.setObjectName('WebSourceLabel') self.web_bible_layout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.web_source_label) - self.web_source_combo_box = QtWidgets.QComboBox(self.web_bible_tab) + self.web_source_combo_box = QtWidgets.QComboBox(self.web_widget) self.web_source_combo_box.setObjectName('WebSourceComboBox') self.web_source_combo_box.addItems(['', '', '']) self.web_source_combo_box.setEnabled(False) @@ -243,31 +243,7 @@ class BibleImportForm(OpenLPWizard): self.web_progress_bar.setObjectName('WebTranslationProgressBar') self.web_progress_bar.setVisible(False) self.web_bible_layout.setWidget(3, QtWidgets.QFormLayout.SpanningRole, self.web_progress_bar) - self.web_tab_widget.addTab(self.web_bible_tab, '') - self.web_proxy_tab = QtWidgets.QWidget() - self.web_proxy_tab.setObjectName('WebProxyTab') - self.web_proxy_layout = QtWidgets.QFormLayout(self.web_proxy_tab) - self.web_proxy_layout.setObjectName('WebProxyLayout') - self.web_server_label = QtWidgets.QLabel(self.web_proxy_tab) - self.web_server_label.setObjectName('WebServerLabel') - self.web_proxy_layout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.web_server_label) - self.web_server_edit = QtWidgets.QLineEdit(self.web_proxy_tab) - self.web_server_edit.setObjectName('WebServerEdit') - self.web_proxy_layout.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.web_server_edit) - self.web_user_label = QtWidgets.QLabel(self.web_proxy_tab) - self.web_user_label.setObjectName('WebUserLabel') - self.web_proxy_layout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.web_user_label) - self.web_user_edit = QtWidgets.QLineEdit(self.web_proxy_tab) - self.web_user_edit.setObjectName('WebUserEdit') - self.web_proxy_layout.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.web_user_edit) - self.web_password_label = QtWidgets.QLabel(self.web_proxy_tab) - self.web_password_label.setObjectName('WebPasswordLabel') - self.web_proxy_layout.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.web_password_label) - self.web_password_edit = QtWidgets.QLineEdit(self.web_proxy_tab) - self.web_password_edit.setObjectName('WebPasswordEdit') - self.web_proxy_layout.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.web_password_edit) - self.web_tab_widget.addTab(self.web_proxy_tab, '') - self.select_stack.addWidget(self.web_tab_widget) + self.select_stack.addWidget(self.web_widget) self.zefania_widget = QtWidgets.QWidget(self.select_page) self.zefania_widget.setObjectName('ZefaniaWidget') self.zefania_layout = QtWidgets.QFormLayout(self.zefania_widget) @@ -427,14 +403,6 @@ class BibleImportForm(OpenLPWizard): self.web_source_combo_box.setItemText(WebDownload.Bibleserver, translate('BiblesPlugin.ImportWizardForm', 'Bibleserver')) self.web_translation_label.setText(translate('BiblesPlugin.ImportWizardForm', 'Bible:')) - self.web_tab_widget.setTabText(self.web_tab_widget.indexOf(self.web_bible_tab), - translate('BiblesPlugin.ImportWizardForm', 'Download Options')) - self.web_server_label.setText(translate('BiblesPlugin.ImportWizardForm', 'Server:')) - self.web_user_label.setText(translate('BiblesPlugin.ImportWizardForm', 'Username:')) - self.web_password_label.setText(translate('BiblesPlugin.ImportWizardForm', 'Password:')) - self.web_tab_widget.setTabText( - self.web_tab_widget.indexOf(self.web_proxy_tab), translate('BiblesPlugin.ImportWizardForm', - 'Proxy Server (Optional)')) self.sword_bible_label.setText(translate('BiblesPlugin.ImportWizardForm', 'Bibles:')) self.sword_folder_label.setText(translate('BiblesPlugin.ImportWizardForm', 'SWORD data folder:')) self.sword_zipfile_label.setText(translate('BiblesPlugin.ImportWizardForm', 'SWORD zip-file:')) @@ -609,11 +577,10 @@ class BibleImportForm(OpenLPWizard): self.web_update_button.setEnabled(False) self.web_progress_bar.setVisible(True) self.web_progress_bar.setValue(0) - proxy_server = self.field('proxy_server') # TODO: Where does critical_error_message_box get %s string from? - for (download_type, extractor) in ((WebDownload.Crosswalk, CWExtract(proxy_server)), - (WebDownload.BibleGateway, BGExtract(proxy_server)), - (WebDownload.Bibleserver, BSExtract(proxy_server))): + for (download_type, extractor) in ((WebDownload.Crosswalk, CWExtract()), + (WebDownload.BibleGateway, BGExtract()), + (WebDownload.Bibleserver, BSExtract())): try: bibles = extractor.get_bibles_from_http() except (urllib.error.URLError, ConnectionError) as err: @@ -679,9 +646,6 @@ class BibleImportForm(OpenLPWizard): self.select_page.registerField('web_biblename', self.web_translation_combo_box) self.select_page.registerField('sword_folder_path', self.sword_folder_path_edit, 'path', PathEdit.pathChanged) self.select_page.registerField('sword_zip_path', self.sword_zipfile_path_edit, 'path', PathEdit.pathChanged) - self.select_page.registerField('proxy_server', self.web_server_edit) - self.select_page.registerField('proxy_username', self.web_user_edit) - self.select_page.registerField('proxy_password', self.web_password_edit) self.license_details_page.registerField('license_version', self.version_name_edit) self.license_details_page.registerField('license_copyright', self.copyright_edit) self.license_details_page.registerField('license_permissions', self.permissions_edit) @@ -706,9 +670,6 @@ class BibleImportForm(OpenLPWizard): self.setField('sword_zip_path', None) self.setField('web_location', WebDownload.Crosswalk) self.setField('web_biblename', self.web_translation_combo_box.currentIndex()) - self.setField('proxy_server', settings.value('proxy address')) - self.setField('proxy_username', settings.value('proxy username')) - self.setField('proxy_password', settings.value('proxy password')) self.setField('license_version', self.version_name_edit.text()) self.version_name_edit.setPlaceholderText(UiStrings().RequiredShowInFooter) self.setField('license_copyright', self.copyright_edit.text()) @@ -765,9 +726,6 @@ class BibleImportForm(OpenLPWizard): BibleFormat.WebDownload, name=license_version, download_source=WebDownload.Names[download_location], download_name=bible, - proxy_server=self.field('proxy_server'), - proxy_username=self.field('proxy_username'), - proxy_password=self.field('proxy_password'), language_id=language_id ) elif bible_type == BibleFormat.Zefania: diff --git a/openlp/plugins/bibles/lib/db.py b/openlp/plugins/bibles/lib/db.py index 040115d56..e42fed5c6 100644 --- a/openlp/plugins/bibles/lib/db.py +++ b/openlp/plugins/bibles/lib/db.py @@ -82,20 +82,19 @@ def init_schema(url): meta_table = Table('metadata', metadata, Column('key', types.Unicode(255), primary_key=True, index=True), - Column('value', types.Unicode(255)),) + Column('value', types.Unicode(255))) book_table = Table('book', metadata, Column('id', types.Integer, primary_key=True), Column('book_reference_id', types.Integer, index=True), Column('testament_reference_id', types.Integer), - Column('name', types.Unicode(50), index=True),) + Column('name', types.Unicode(50), index=True)) verse_table = Table('verse', metadata, Column('id', types.Integer, primary_key=True, index=True), - Column('book_id', types.Integer, ForeignKey( - 'book.id'), index=True), + Column('book_id', types.Integer, ForeignKey('book.id'), index=True), Column('chapter', types.Integer, index=True), Column('verse', types.Integer, index=True), - Column('text', types.UnicodeText, index=True),) + Column('text', types.UnicodeText, index=True)) try: class_mapper(BibleMeta) diff --git a/openlp/plugins/bibles/lib/importers/http.py b/openlp/plugins/bibles/lib/importers/http.py index 4eee26256..c624210be 100644 --- a/openlp/plugins/bibles/lib/importers/http.py +++ b/openlp/plugins/bibles/lib/importers/http.py @@ -94,9 +94,8 @@ class BGExtract(RegistryProperties): """ NAME = 'BibleGateway' - def __init__(self, proxy_url=None): - log.debug('BGExtract.init(proxy_url="{url}")'.format(url=proxy_url)) - self.proxy_url = proxy_url + def __init__(self): + log.debug('BGExtract.__init__()') socket.setdefaulttimeout(30) def _remove_elements(self, parent, tag, class_=None): @@ -358,9 +357,8 @@ class BSExtract(RegistryProperties): """ NAME = 'BibleServer' - def __init__(self, proxy_url=None): - log.debug('BSExtract.init("{url}")'.format(url=proxy_url)) - self.proxy_url = proxy_url + def __init__(self): + log.debug('BSExtract.__init__()') socket.setdefaulttimeout(30) def get_bible_chapter(self, version, book_name, chapter): @@ -461,9 +459,8 @@ class CWExtract(RegistryProperties): """ NAME = 'Crosswalk' - def __init__(self, proxy_url=None): - log.debug('CWExtract.init("{url}")'.format(url=proxy_url)) - self.proxy_url = proxy_url + def __init__(self): + log.debug('CWExtract.__init__()') socket.setdefaulttimeout(30) def get_bible_chapter(self, version, book_name, chapter): @@ -595,19 +592,9 @@ class HTTPBible(BibleImport, RegistryProperties): super().__init__(*args, **kwargs) self.download_source = kwargs['download_source'] self.download_name = kwargs['download_name'] - # TODO: Clean up proxy stuff. We probably want one global proxy per connection type (HTTP and HTTPS) at most. - self.proxy_server = None - self.proxy_username = None - self.proxy_password = None self.language_id = None if 'path' in kwargs: self.path = kwargs['path'] - if 'proxy_server' in kwargs: - self.proxy_server = kwargs['proxy_server'] - if 'proxy_username' in kwargs: - self.proxy_username = kwargs['proxy_username'] - if 'proxy_password' in kwargs: - self.proxy_password = kwargs['proxy_password'] if 'language_id' in kwargs: self.language_id = kwargs['language_id'] @@ -621,20 +608,12 @@ class HTTPBible(BibleImport, RegistryProperties): 'Registering Bible and loading books...')) self.save_meta('download_source', self.download_source) self.save_meta('download_name', self.download_name) - if self.proxy_server: - self.save_meta('proxy_server', self.proxy_server) - if self.proxy_username: - # Store the proxy userid. - self.save_meta('proxy_username', self.proxy_username) - if self.proxy_password: - # Store the proxy password. - self.save_meta('proxy_password', self.proxy_password) if self.download_source.lower() == 'crosswalk': - handler = CWExtract(self.proxy_server) + handler = CWExtract() elif self.download_source.lower() == 'biblegateway': - handler = BGExtract(self.proxy_server) + handler = BGExtract() elif self.download_source.lower() == 'bibleserver': - handler = BSExtract(self.proxy_server) + handler = BSExtract() books = handler.get_books_from_http(self.download_name) if not books: log.error('Importing books from {source} - download name: "{name}" ' @@ -722,11 +701,11 @@ class HTTPBible(BibleImport, RegistryProperties): log.debug('HTTPBible.get_chapter("{book}", "{chapter}")'.format(book=book, chapter=chapter)) log.debug('source = {source}'.format(source=self.download_source)) if self.download_source.lower() == 'crosswalk': - handler = CWExtract(self.proxy_server) + handler = CWExtract() elif self.download_source.lower() == 'biblegateway': - handler = BGExtract(self.proxy_server) + handler = BGExtract() elif self.download_source.lower() == 'bibleserver': - handler = BSExtract(self.proxy_server) + handler = BSExtract() return handler.get_bible_chapter(self.download_name, book, chapter) def get_books(self): diff --git a/openlp/plugins/bibles/lib/manager.py b/openlp/plugins/bibles/lib/manager.py index 1e1b4243d..fce411870 100644 --- a/openlp/plugins/bibles/lib/manager.py +++ b/openlp/plugins/bibles/lib/manager.py @@ -116,7 +116,6 @@ class BibleManager(LogMixin, RegistryProperties): self.web = 'Web' self.db_cache = None 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 self.reload_bibles() @@ -149,11 +148,8 @@ class BibleManager(LogMixin, RegistryProperties): if self.db_cache[name].is_web_bible: 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=file_path, download_source=source.value, download_name=download_name) - if meta_proxy: - web_bible.proxy_server = meta_proxy.value self.db_cache[name] = web_bible log.debug('Bibles reloaded') diff --git a/openlp/plugins/bibles/lib/upgrade.py b/openlp/plugins/bibles/lib/upgrade.py index c53f9d324..76fa49ca0 100644 --- a/openlp/plugins/bibles/lib/upgrade.py +++ b/openlp/plugins/bibles/lib/upgrade.py @@ -24,8 +24,16 @@ The :mod:`upgrade` module provides a way for the database and schema that is the """ import logging +from PyQt5 import QtWidgets +from sqlalchemy import Table +from sqlalchemy.sql.expression import delete, select + +from openlp.core.common.i18n import translate +from openlp.core.common.settings import ProxyMode, Settings +from openlp.core.lib.db import get_upgrade_op + log = logging.getLogger(__name__) -__version__ = 1 +__version__ = 2 # TODO: When removing an upgrade path the ftw-data needs updating to the minimum supported version @@ -36,3 +44,48 @@ def upgrade_1(session, metadata): This upgrade renamed a number of keys to a single naming convention. """ log.info('No upgrades to perform') + + +def upgrade_2(session, metadata): + """ + Remove the individual proxy settings, after the implementation of central proxy settings. + Added in 2.5 (3.0 development) + """ + # TODO: Test + settings = Settings() + op = get_upgrade_op(session) + metadata_table = Table('metadata', metadata, autoload=True) + proxy, = session.execute(select([metadata_table.c.value], metadata_table.c.key == 'proxy_server')).first() or ('', ) + if proxy and not \ + (proxy == settings.value('advanced/proxy http') or proxy == settings.value('advanced/proxy https')): + http_proxy = '' + https_proxy = '' + name, = session.execute(select([metadata_table.c.value], metadata_table.c.key == 'name')).first() + msg_box = QtWidgets.QMessageBox() + msg_box.setText(translate('BiblesPlugin', f'The proxy server {proxy} was found in the bible {name}.
' + f'Would you like to set it as the proxy for OpenLP?')) + msg_box.setIcon(QtWidgets.QMessageBox.Question) + msg_box.addButton(QtWidgets.QMessageBox.No) + http_button = msg_box.addButton('http', QtWidgets.QMessageBox.ActionRole) + both_button = msg_box.addButton(translate('BiblesPlugin', 'both'), QtWidgets.QMessageBox.ActionRole) + https_button = msg_box.addButton('https', QtWidgets.QMessageBox.ActionRole) + msg_box.setDefaultButton(both_button) + msg_box.exec() + + clicked_button = msg_box.clickedButton() + if clicked_button in [http_button, both_button]: + http_proxy = proxy + settings.setValue('advanced/proxy http', proxy) + if clicked_button in [https_button, both_button]: + https_proxy = proxy + settings.setValue('advanced/proxy https', proxy) + if http_proxy or https_proxy: + username, = session.execute(select([metadata_table.c.value], metadata_table.c.key == 'proxy_username')).first() + proxy, = session.execute(select([metadata_table.c.value], metadata_table.c.key == 'proxy_password')).first() + settings.setValue('advanced/proxy username', username) + settings.setValue('advanced/proxy password', proxy) + settings.setValue('advanced/proxy mode', ProxyMode.MANUAL_PROXY) + + op.execute(delete(metadata_table, metadata_table.c.key == 'proxy_server')) + op.execute(delete(metadata_table, metadata_table.c.key == 'proxy_username')) + op.execute(delete(metadata_table, metadata_table.c.key == 'proxy_password')) diff --git a/tests/functional/openlp_plugins/bibles/test_upgrade.py b/tests/functional/openlp_plugins/bibles/test_upgrade.py new file mode 100644 index 000000000..0eb33338d --- /dev/null +++ b/tests/functional/openlp_plugins/bibles/test_upgrade.py @@ -0,0 +1,219 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2018 OpenLP Developers # +# --------------------------------------------------------------------------- # +# This program is free software; you can redistribute it and/or modify it # +# under the terms of the GNU General Public License as published by the Free # +# Software Foundation; version 2 of the License. # +# # +# This program is distributed in the hope that it will be useful, but WITHOUT # +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # +# more details. # +# # +# You should have received a copy of the GNU General Public License along # +# with this program; if not, write to the Free Software Foundation, Inc., 59 # +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # +############################################################################### +""" +This module contains tests for the upgrade submodule of the Bibles plugin. +""" +import os +import shutil +from pathlib import Path +from tempfile import mkdtemp +from unittest import TestCase +from unittest.mock import MagicMock, call, patch + +from PyQt5 import QtWidgets +from sqlalchemy import create_engine + +from openlp.core.common.settings import ProxyMode +from openlp.core.lib.db import upgrade_db +from openlp.plugins.bibles.lib import upgrade +from tests.helpers.testmixin import TestMixin +from tests.utils.constants import RESOURCE_PATH + + +class TestUpgrade(TestCase, TestMixin): + """ + Test the `upgrade_2` function in the :mod:`upgrade` module when the db does not contains proxy metadata + """ + + def setUp(self): + """ + Setup for tests + """ + self.tmp_path = Path(mkdtemp()) + db_path = RESOURCE_PATH / 'bibles' / 'web-bible-2.4.6-v1.sqlite' + db_tmp_path = self.tmp_path / 'web-bible-2.4.6-v1.sqlite' + shutil.copyfile(db_path, db_tmp_path) + self.db_url = 'sqlite:///' + str(db_tmp_path) + + patched_settings = patch('openlp.plugins.bibles.lib.upgrade.Settings') + self.mocked_settings = patched_settings.start() + self.addCleanup(patched_settings.stop) + self.mocked_settings_instance = MagicMock() + self.mocked_settings.return_value = self.mocked_settings_instance + + patched_message_box = patch('openlp.plugins.bibles.lib.upgrade.QtWidgets.QMessageBox') + self.mocked_message_box = patched_message_box.start() + self.addCleanup(patched_message_box.stop) + + def tearDown(self): + """ + Clean up after tests + """ + # Ignore errors since windows can have problems with locked files + shutil.rmtree(self.tmp_path, ignore_errors=True) + + def test_upgrade_2_none_selected(self): + """ + Test that upgrade 2 completes properly when the user chooses not to use a proxy ('No') + """ + # GIVEN: An version 1 web bible with proxy settings + + # WHEN: Calling upgrade_db and the user has 'clicked' the 'No' button + upgrade_db(self.db_url, upgrade) + + # THEN: The proxy meta data should have been removed, and the version should have been changed to version 2 + self.mocked_message_box.assert_not_called() + engine = create_engine(self.db_url) + conn = engine.connect() + assert conn.execute('SELECT * FROM metadata WHERE key = "version"').first().value == '2' + + +class TestProxyMetaUpgrade(TestCase, TestMixin): + """ + Test the `upgrade_2` function in the :mod:`upgrade` module when the db contains proxy metadata + """ + + def setUp(self): + """ + Setup for tests + """ + self.tmp_path = Path(mkdtemp()) + db_path = RESOURCE_PATH / 'bibles' / 'web-bible-2.4.6-proxy-meta-v1.sqlite' + db_tmp_path = self.tmp_path / 'web-bible-2.4.6-proxy-meta-v1.sqlite' + shutil.copyfile(db_path, db_tmp_path) + self.db_url = 'sqlite:///' + str(db_tmp_path) + + patched_settings = patch('openlp.plugins.bibles.lib.upgrade.Settings') + self.mocked_settings = patched_settings.start() + self.addCleanup(patched_settings.stop) + self.mocked_settings_instance = MagicMock() + self.mocked_settings.return_value = self.mocked_settings_instance + + patched_message_box = patch('openlp.plugins.bibles.lib.upgrade.QtWidgets.QMessageBox') + mocked_message_box = patched_message_box.start() + self.addCleanup(patched_message_box.stop) + self.mocked_no_button = MagicMock() + self.mocked_http_button = MagicMock() + self.mocked_both_button = MagicMock() + self.mocked_https_button = MagicMock() + self.mocked_message_box_instance = MagicMock( + **{'addButton.side_effect': [self.mocked_no_button, self.mocked_http_button, + self.mocked_both_button, self.mocked_https_button]}) + mocked_message_box.return_value = self.mocked_message_box_instance + + def tearDown(self): + """ + Clean up after tests + """ + # Ignore errors since windows can have problems with locked files + shutil.rmtree(self.tmp_path, ignore_errors=True) + + def test_upgrade_2_none_selected(self): + """ + Test that upgrade 2 completes properly when the user chooses not to use a proxy ('No') + """ + # GIVEN: An version 1 web bible with proxy settings + + # WHEN: Calling upgrade_db and the user has 'clicked' the 'No' button + self.mocked_message_box_instance.clickedButton.return_value = self.mocked_no_button + upgrade_db(self.db_url, upgrade) + + # THEN: The proxy meta data should have been removed, and the version should have been changed to version 2 + engine = create_engine(self.db_url) + conn = engine.connect() + assert len(conn.execute('SELECT * FROM metadata WHERE key = "proxy_server"').fetchall()) == 0 + assert len(conn.execute('SELECT * FROM metadata WHERE key = "proxy_username"').fetchall()) == 0 + assert len(conn.execute('SELECT * FROM metadata WHERE key = "proxy_password"').fetchall()) == 0 + assert conn.execute('SELECT * FROM metadata WHERE key = "version"').first().value == '2' + self.mocked_settings_instance.setValue.assert_not_called() + + + def test_upgrade_2_http_selected(self): + """ + Test that upgrade 2 completes properly when the user chooses to use a HTTP proxy + """ + # GIVEN: An version 1 web bible with proxy settings + + # WHEN: Calling upgrade_db and the user has 'clicked' the 'HTTP' button + self.mocked_message_box_instance.clickedButton.return_value = self.mocked_http_button + upgrade_db(self.db_url, upgrade) + + # THEN: The proxy meta data should have been removed, the version should have been changed to version 2, and the + # proxy server saved to the settings + engine = create_engine(self.db_url) + conn = engine.connect() + assert len(conn.execute('SELECT * FROM metadata WHERE key = "proxy_server"').fetchall()) == 0 + assert len(conn.execute('SELECT * FROM metadata WHERE key = "proxy_username"').fetchall()) == 0 + assert len(conn.execute('SELECT * FROM metadata WHERE key = "proxy_password"').fetchall()) == 0 + assert conn.execute('SELECT * FROM metadata WHERE key = "version"').first().value == '2' + + assert self.mocked_settings_instance.setValue.call_args_list == [ + call('advanced/proxy http', 'proxy_server'), call('advanced/proxy username', 'proxy_username'), + call('advanced/proxy password', 'proxy_password'), call('advanced/proxy mode', ProxyMode.MANUAL_PROXY)] + + def test_upgrade_2_https_selected(self): + """ + Tcest that upgrade 2 completes properly when the user chooses to use a HTTPS proxy + """ + # GIVEN: An version 1 web bible with proxy settings + + # WHEN: Calling upgrade_db and the user has 'clicked' the 'HTTPS' button + self.mocked_message_box_instance.clickedButton.return_value = self.mocked_https_button + upgrade_db(self.db_url, upgrade) + + # THEN: The proxy settings should have been removed, the version should have been changed to version 2, and the + # proxy server saved to the settings + engine = create_engine(self.db_url) + conn = engine.connect() + assert len(conn.execute('SELECT * FROM metadata WHERE key = "proxy_server"').fetchall()) == 0 + assert len(conn.execute('SELECT * FROM metadata WHERE key = "proxy_username"').fetchall()) == 0 + assert len(conn.execute('SELECT * FROM metadata WHERE key = "proxy_password"').fetchall()) == 0 + assert conn.execute('SELECT * FROM metadata WHERE key = "version"').first().value == '2' + + assert self.mocked_settings_instance.setValue.call_args_list == [ + call('advanced/proxy https', 'proxy_server'), call('advanced/proxy username', 'proxy_username'), + call('advanced/proxy password', 'proxy_password'), call('advanced/proxy mode', ProxyMode.MANUAL_PROXY)] + + def test_upgrade_2_both_selected(self): + """ + Tcest that upgrade 2 completes properly when the user chooses to use a both HTTP and HTTPS proxies + """ + + # GIVEN: An version 1 web bible with proxy settings + + # WHEN: Calling upgrade_db + self.mocked_message_box_instance.clickedButton.return_value = self.mocked_both_button + upgrade_db(self.db_url, upgrade) + + # THEN: The proxy settings should have been removed, the version should have been changed to version 2, and the + # proxy server saved to the settings + engine = create_engine(self.db_url) + conn = engine.connect() + assert len(conn.execute('SELECT * FROM metadata WHERE key = "proxy_server"').fetchall()) == 0 + assert len(conn.execute('SELECT * FROM metadata WHERE key = "proxy_username"').fetchall()) == 0 + assert len(conn.execute('SELECT * FROM metadata WHERE key = "proxy_password"').fetchall()) == 0 + assert conn.execute('SELECT * FROM metadata WHERE key = "version"').first().value == '2' + + assert self.mocked_settings_instance.setValue.call_args_list == [ + call('advanced/proxy http', 'proxy_server'), call('advanced/proxy https', 'proxy_server'), + call('advanced/proxy username', 'proxy_username'), call('advanced/proxy password', 'proxy_password'), + call('advanced/proxy mode', ProxyMode.MANUAL_PROXY)] diff --git a/tests/resources/bibles/tests.sqlite b/tests/resources/bibles/tests.sqlite index b6af457d77f217674aa5b98fbb7f88fee9c930c0..a49d3b21a62f4ceed236898f30eddaed9859ee91 100644 GIT binary patch delta 30 mcmZp;z}Rqsae_2s^h6nF)@TMjnGG9LO4u2VHs54dPXGX&_zBSf delta 30 mcmZp;z}Rqsae_2s)I=F))+h$uXUrQ@O4u0F*y->NZVNH%-H~P6G21zs6Q< zzs%2Wn}HZDNE;vx{ul$HA&p-H!Gxe15&~2Ww1G5D+62>trh#BgFbxSYMuBYzanH4# z*lor?lh70$OaA7bbMHI%zRz`{zVCbP`ExUwqN!iB3OS>wk2;zilH@q1>kfzGPX1Q; zyZZ9-Mng5=Kc$-XyEX50?B4!84gTd&l*b&9fM2k0u%z~e)~o(OeOR4T-r{L`5dk7V z1c(3;AOb{y2oM1xP)FdL;_?srrCcVTGOuK=Brll-+e|K|jg_KV5P>>57mv-y^;3!C z@w@a}hv;V$x)`SSRSt?wd0qZqzhu;xESgt}VxY2dJ?yV3hGFEKXQRa}Sk`hflM=kRhHJYaivVErc5Rk zxq8y&_WS+PwOr9y$eQabaaWF?k4?>Y5g-CYfCvx)B0vO) z01+Sp?{fkHMUMK=6iDK=dopX}mrBNxnXvL^*eiDpt(b*e#L)U0HWl>9 z;r>NyWwnr5N*5=qjY?;e>>I#k4#kKm>>Y5g-CYfCvx)B0vO)01+Sp|91lIa#XtAZcsq(l4`65uO^43HD|%Y ztpo`#_?vK8jcwp=l_OHUP2gHjRgHnhX1S*_s%#82w8*{E+I2q`mfPh1^-S3baC+sS zc*uks0C@j@LV48ze}h-ySMU^kAHD{khfl#OWZ-T%4SS&r{NQG9vA?k2vFF)O_!InJ zVD~edrPv+p6g$kKtdsdzllG4GXYD2JS?$N#@bGG z!@PIaZ-&7YgQO8}?%y+V7lAXR>C_%B%at z@O{qtOtEA)@5Ly*mlvPU~21BO;#JkY+%qiXXW^z$^eGG0cUKnXe<39=yxJ+WtRwcIcG}C zrVt8K$5xGjFK zxy-}X0ERt*8U6vu7sbcXW?L~A+B(g@fRMve@rwffQ?|h)Upq$q?dPrhl5Snp&-0z+ zpPg2pDCCPTE@pAdZ2lNO+ad~WnJrlS7Waa!wuwS*v;1?CvpuaCwY5%L3u=q#Xo=e# zZGkn5!FEOhtl?y>n#gH<%hz(6in*XVqMy#>muyWD zp%N{J%!^^jJEEUAawRj%JRNGFloj z>S{c0miTXGm9l#b&3Mc_!TR~BdO8h@dQtr zdCttn`~MT_(+>EM_JaB*d_?oYLz=CfgEQLC)YrgL{{#1HNo`X7jrIonjCMc!COpaB z(H?^Vc%5B`=IJ&wuEs01+SpM1Tko0U|&IhyW2F0-K)zlRKn! zZ({;)s-mwE^U49K>}edsX4%g;ip{c@aYT`WJS%*Q!`QBP7>7KvUlRVsLCjXXivy}0 zlB&MNejFk^i+y-Ucg?TZi_w}_F^+d%Z}BO{Fn5bbu?Np!-Jcl6yTLe9#hVzxqHDfH z6r(jyVi<4v*7*@bn5pw3cH=3ReTYGXgC4|ypv3)$er%xkunQaLJ4EoLE1pANxh)Kb zUYvN%ZPtMCXs44;Ef!zbZma0LpGg^TcE{)E6h|6l#4 zU;+-oI1Ixs=!PAz674|ZFkv~1~410>*U_WHn+2ian_9*)j zdx$;A9$?qlNBJV?MFfZd5g-CYfCvx)B0vO)01+Spn~XrCEJ+g+sIeI8v16!5kD?wq zf_nHc>Y+oZ2M?kiIDoo;KkB}HsC)OKj*p{`jiK(@gE~5jIx>P9jiL?@qYe$B?%s_$ zIEXqhfZE@Wx@#9|B!b%4huYhVx^pLLPY-H$H)>ZGYG)^EM+d5|qlWo6`6X$`4%AQx zb^CVIU=VfNHq<}>b?a7CzaO=|9o6SU-LeI>tqrxc6}6=WwYeD;0F^ORO+!^xR7FAc zdQm+d)TSm>w;R>vLTzkBZD>H1WmKnAmOUPP{@>BO$v$3mIz)g75CI}U1c(3;AOb{y z2oM1xKm>?D9RYm)pRWJwkWfGbhyW2F0z`la5CI}U1c(3;AOb{S(-ENS|4p~0bV5Xc z2oM1xKm>>Y5g-CYfCvx)B0vOgBS6>xw;`bf5g-CYfCvx)B0vO)01+SpM1TkoflWt% zuKzdPmeL6k0U|&IhyW2F0z`la5CI}U1c(3;xQzf^|KEm$5=4Lq5CI}U1c(3;AOb{y O2oM1xKm;}&f&T(jC0Qf@ literal 0 HcmV?d00001 diff --git a/tests/resources/bibles/web-bible-2.4.6-v1.sqlite b/tests/resources/bibles/web-bible-2.4.6-v1.sqlite new file mode 100644 index 0000000000000000000000000000000000000000..6d61b153e7f2822dd1897dade200db6d1ed06cfb GIT binary patch literal 49152 zcmeI4e{36P8OMEhiGSU_@0%ZWuCD9)@~erPq=}Oz>F?G_-KJ^krfHbkNnkF?HMU~= zGN0Wa1F^M2>ITx_k1`M%(f~0Knvj@=gaAzgZ6HmPHo-KZsvsB>OhZD9QD7TFyw7Vp zwcCt8m?lB=IPy2|^W6JB?|YxSSiSF^d*<{^rf3=G?Ly8h8Y8YImn69k8-~l}I?LZ0 ze^+0A-e{->{I^uoe!u2f*Uqj73HygjRiALNB>Nft22GK-NRReA?NMz~eTS#rCcVzWL?T!N?ouDj+I(Wo2x~uAOdZ2E)kzk7{`-G z5@(G&yBMdEhUjMWRt80<{BFHRm(2Q-Me9;gbX3=`2it3kK^Q&lTW@g-w!M!&)xb;q5I#*w;vNZ4dl`=~;Wim_A zE63a(UDu_nxuUs{wQjG(QyD)WpO{Hh8T);F{PE;`;#gwNNS>NEk{_9w={Nko>K<3N zL8@{*s@)+~-i?|*g8F>o>^y9pojZOqK6lPIkvM1c@})uU=&8BH@ncEI^;Q`gbBUvg zxkPd@amIk0=&Gh>{7_5 zMEVx()ysvNp2dH3YU$0xFZLpnmkHpb9FB6qgZ*4CiNlW zX;qY-)+PI5K5LsxVpsEv7ecbq7gcB!yvO%`m&xoBorR7nXj@R;~|V}AA}mzPbu z*fhJ3y~KXY9%2tLlie!+e2DAD(ntUaAOR$R1dsp{Kmter2_OL^fCT<83A8CO>2AA0 zWlE=1V>S4R5|P%N1s}H(B(M^^(5=Qc@Nfj<{{@C-rAJ!3?uWuktI~HnQ+5JmzY-D; znQ#Mu@JS}r*In!{>~;1F_8j{z`#Spqdzf8j8Fnu_!Nyr9)0v09L;pm7Ltmyp;!p5@ zkzS(?U848UAzX>yKCk^vGTKJAwFn)ZVB zl=isxi1wgnYbout7T020ht{e!sDI~+z!wrg0!RP}AOR$R1dsp{Kms3_K=VW+|5k7N zggnO|T+f;N~UcTGJT`aCv7>7nYHs7Gw-zR68T-wvo_y6 z0{Qk4dD71EJD||DWf)SO!(tVg)m6wf#UK}n$%$1yT4aVqW=K9}0yN%d(;#Ge2Km&J zb|I55rbTwA$nNA5*JQ~6WP=0poSoxC)qV&A{c?P<=%{@n=#v4rx% zl~0<571|3ysFw%DV%oY$dU)&$^_=AESxjfxb_gTe@3r`M2nrc9t#v~p*ga(~FPj#H;wXK^WK;&)JSYEZX5|+sdk2#h9&Awqu!E2!def2%p%>k}VL0wj}PiR(RMF zgs?j}!%vWWQJjtr-3+<#=4t)~gdFUOE(++!9g|0aHi-J#&e-{7!#;1E;YX66J1qfG zD3DlO%)%ja_+$KZlPI)lwqWyH+zXD@DhjpE^7AC;_*x)pZJD+gv}UoPIpJ`$1==Kr zH6^XInBUJJ4zR@i1+#@hyPNWZo-Xin4JK6E=vU}X(neomUGzG6mzLP~v{%@-=`-vmRcJfaxJR%;P3|YWOdlZq z2x3@Gtg3w&Gpv)s(PQ^)2>5 z7vWj#hC8}ze#JOMYhJ||+nrx=CY9UjFl*n`{t#0cCChM6kf#4r?H^Ce;st$7kd zaLc#Oj~IkZofokacDd|B3;-PPAo>L*?mzTF1H6YF&;Z{d3Oil#9D2)bVL0@_#A|ND zb|_l58oJ9HDn3IOaH^UNoxn*r3?0xC_CmYB3*LeO)hotAM68Uv3fsynv$A36C;Wu1 zFjv`32*Hqwldz>p>6U73gdj{^^$<40pt6CW1M@rFgEkmkYaIl{@vZm5Zl4J*fzGAweYKZH*?W<>0kMC18>mR=uP@6e|q2r`W$_RexE)?pQKOF$LW{p zb@~W>h+d_i?>N;p!8)zT^x@i+=Yb$6=3utpQXj2m?W1y6R5(27epsEV$_k;R; zpk6Pi#{=qigElsTHZ*`L3aBhAiq8kn|GSzt*y)A4g9MNO5c_5X%D zq_{&!00|%gB!C2v01`j~NB{{S0VIF~?jnHK|92t51QI|3NB{{S0VIF~kN^@u0!RP} JAb|}>;NP@+CVBt> literal 0 HcmV?d00001 From aa744e0d99cceb5f1bd678cc9bea894128453bd5 Mon Sep 17 00:00:00 2001 From: Philip Ridout Date: Mon, 22 Oct 2018 21:42:25 +0100 Subject: [PATCH 33/33] PEP fixes --- openlp/core/common/settings.py | 3 --- openlp/core/lib/__init__.py | 2 +- openlp/plugins/bibles/lib/upgrade.py | 4 ++-- tests/functional/openlp_core/common/test_i18n.py | 1 + tests/functional/openlp_plugins/bibles/test_upgrade.py | 9 ++++----- 5 files changed, 8 insertions(+), 11 deletions(-) diff --git a/openlp/core/common/settings.py b/openlp/core/common/settings.py index 6fe0429cb..830994546 100644 --- a/openlp/core/common/settings.py +++ b/openlp/core/common/settings.py @@ -275,9 +275,6 @@ class Settings(QtCore.QSettings): ('songuasge/db hostname', 'songusage/db hostname', []), ('songuasge/db database', 'songusage/db database', []), ('presentations / Powerpoint Viewer', '', []), - ('songuasge/db hostname', 'songusage/db hostname', []), - ('songuasge/db hostname', 'songusage/db hostname', []), - ('songuasge/db hostname', 'songusage/db hostname', []), ('bibles/proxy name', '', []), # Just remove these bible proxy settings. They weren't used in 2.4! ('bibles/proxy address', '', []), ('bibles/proxy username', '', []), diff --git a/openlp/core/lib/__init__.py b/openlp/core/lib/__init__.py index 0111c13e5..e15f81ab6 100644 --- a/openlp/core/lib/__init__.py +++ b/openlp/core/lib/__init__.py @@ -609,4 +609,4 @@ def create_separated_list(string_list): last=string_list[-1]) else: list_to_string = '' - return list_to_string \ No newline at end of file + return list_to_string diff --git a/openlp/plugins/bibles/lib/upgrade.py b/openlp/plugins/bibles/lib/upgrade.py index 76fa49ca0..763e40c6b 100644 --- a/openlp/plugins/bibles/lib/upgrade.py +++ b/openlp/plugins/bibles/lib/upgrade.py @@ -51,7 +51,6 @@ def upgrade_2(session, metadata): Remove the individual proxy settings, after the implementation of central proxy settings. Added in 2.5 (3.0 development) """ - # TODO: Test settings = Settings() op = get_upgrade_op(session) metadata_table = Table('metadata', metadata, autoload=True) @@ -80,7 +79,8 @@ def upgrade_2(session, metadata): https_proxy = proxy settings.setValue('advanced/proxy https', proxy) if http_proxy or https_proxy: - username, = session.execute(select([metadata_table.c.value], metadata_table.c.key == 'proxy_username')).first() + username, = session.execute( + select([metadata_table.c.value], metadata_table.c.key == 'proxy_username')).first() proxy, = session.execute(select([metadata_table.c.value], metadata_table.c.key == 'proxy_password')).first() settings.setValue('advanced/proxy username', username) settings.setValue('advanced/proxy password', proxy) diff --git a/tests/functional/openlp_core/common/test_i18n.py b/tests/functional/openlp_core/common/test_i18n.py index 7dadcb976..a4b896c6b 100644 --- a/tests/functional/openlp_core/common/test_i18n.py +++ b/tests/functional/openlp_core/common/test_i18n.py @@ -162,6 +162,7 @@ def test_check_same_instance(): def test_get_language_from_settings(): assert LanguageManager.get_language() == 'en' + def test_get_language_from_settings_returns_unchanged_if_unknown_format(): Settings().setValue('core/language', '(foobar)') assert LanguageManager.get_language() == '(foobar)' diff --git a/tests/functional/openlp_plugins/bibles/test_upgrade.py b/tests/functional/openlp_plugins/bibles/test_upgrade.py index 0eb33338d..1e2520391 100644 --- a/tests/functional/openlp_plugins/bibles/test_upgrade.py +++ b/tests/functional/openlp_plugins/bibles/test_upgrade.py @@ -146,7 +146,6 @@ class TestProxyMetaUpgrade(TestCase, TestMixin): assert conn.execute('SELECT * FROM metadata WHERE key = "version"').first().value == '2' self.mocked_settings_instance.setValue.assert_not_called() - def test_upgrade_2_http_selected(self): """ Test that upgrade 2 completes properly when the user chooses to use a HTTP proxy @@ -167,7 +166,7 @@ class TestProxyMetaUpgrade(TestCase, TestMixin): assert conn.execute('SELECT * FROM metadata WHERE key = "version"').first().value == '2' assert self.mocked_settings_instance.setValue.call_args_list == [ - call('advanced/proxy http', 'proxy_server'), call('advanced/proxy username', 'proxy_username'), + call('advanced/proxy http', 'proxy_server'), call('advanced/proxy username', 'proxy_username'), call('advanced/proxy password', 'proxy_password'), call('advanced/proxy mode', ProxyMode.MANUAL_PROXY)] def test_upgrade_2_https_selected(self): @@ -214,6 +213,6 @@ class TestProxyMetaUpgrade(TestCase, TestMixin): assert conn.execute('SELECT * FROM metadata WHERE key = "version"').first().value == '2' assert self.mocked_settings_instance.setValue.call_args_list == [ - call('advanced/proxy http', 'proxy_server'), call('advanced/proxy https', 'proxy_server'), - call('advanced/proxy username', 'proxy_username'), call('advanced/proxy password', 'proxy_password'), - call('advanced/proxy mode', ProxyMode.MANUAL_PROXY)] + call('advanced/proxy http', 'proxy_server'), call('advanced/proxy https', 'proxy_server'), + call('advanced/proxy username', 'proxy_username'), call('advanced/proxy password', 'proxy_password'), + call('advanced/proxy mode', ProxyMode.MANUAL_PROXY)]