diff --git a/openlp/core/__init__.py b/openlp/core/__init__.py index b663fc55b..db135ef10 100644 --- a/openlp/core/__init__.py +++ b/openlp/core/__init__.py @@ -27,26 +27,26 @@ All the core functions of the OpenLP application including the GUI, settings, logging and a plugin framework are contained within the openlp.core module. """ -import os -import sys -import logging import argparse -from traceback import format_exception +import logging +import os import shutil +import sys import time +from traceback import format_exception + from PyQt5 import QtCore, QtGui, QtWidgets from openlp.core.common import Registry, OpenLPMixin, AppLocation, LanguageManager, Settings, UiStrings, \ check_directory_exists, is_macosx, is_win, translate +from openlp.core.common.versionchecker import VersionThread, get_application_version from openlp.core.lib import ScreenList from openlp.core.resources import qInitResources -from openlp.core.ui.mainwindow import MainWindow -from openlp.core.ui.firsttimelanguageform import FirstTimeLanguageForm -from openlp.core.ui.firsttimeform import FirstTimeForm -from openlp.core.ui.exceptionform import ExceptionForm from openlp.core.ui import SplashScreen -from openlp.core.utils import VersionThread, get_application_version - +from openlp.core.ui.exceptionform import ExceptionForm +from openlp.core.ui.firsttimeform import FirstTimeForm +from openlp.core.ui.firsttimelanguageform import FirstTimeLanguageForm +from openlp.core.ui.mainwindow import MainWindow __all__ = ['OpenLP', 'main'] diff --git a/openlp/core/common/versionchecker.py b/openlp/core/common/versionchecker.py new file mode 100644 index 000000000..5dcab6a6f --- /dev/null +++ b/openlp/core/common/versionchecker.py @@ -0,0 +1,171 @@ +import logging +import os +import platform +import sys +import time +import urllib.error +import urllib.parse +import urllib.request +from datetime import datetime +from distutils.version import LooseVersion +from subprocess import Popen, PIPE + +from openlp.core.common import AppLocation, Settings + +from PyQt5 import QtCore + +log = logging.getLogger(__name__) + +APPLICATION_VERSION = {} +CONNECTION_TIMEOUT = 30 +CONNECTION_RETRIES = 2 + + +class VersionThread(QtCore.QThread): + """ + A special Qt thread class to fetch the version of OpenLP from the website. + This is threaded so that it doesn't affect the loading time of OpenLP. + """ + def __init__(self, main_window): + """ + Constructor for the thread class. + + :param main_window: The main window Object. + """ + log.debug("VersionThread - Initialise") + super(VersionThread, self).__init__(None) + self.main_window = main_window + + def run(self): + """ + Run the thread. + """ + self.sleep(1) + log.debug('Version thread - run') + app_version = get_application_version() + version = check_latest_version(app_version) + log.debug("Versions %s and %s " % (LooseVersion(str(version)), LooseVersion(str(app_version['full'])))) + if LooseVersion(str(version)) > LooseVersion(str(app_version['full'])): + self.main_window.openlp_version_check.emit('%s' % version) + + +def get_application_version(): + """ + Returns the application version of the running instance of OpenLP:: + + {'full': '1.9.4-bzr1249', 'version': '1.9.4', 'build': 'bzr1249'} + """ + global APPLICATION_VERSION + if APPLICATION_VERSION: + return APPLICATION_VERSION + if '--dev-version' in sys.argv or '-d' in sys.argv: + # NOTE: The following code is a duplicate of the code in setup.py. Any fix applied here should also be applied + # there. + + # Get the revision of this tree. + bzr = Popen(('bzr', 'revno'), stdout=PIPE) + tree_revision, error = bzr.communicate() + tree_revision = tree_revision.decode() + code = bzr.wait() + if code != 0: + raise Exception('Error running bzr log') + + # Get all tags. + bzr = Popen(('bzr', 'tags'), stdout=PIPE) + output, error = bzr.communicate() + code = bzr.wait() + if code != 0: + raise Exception('Error running bzr tags') + tags = list(map(bytes.decode, output.splitlines())) + if not tags: + tag_version = '0.0.0' + tag_revision = '0' + else: + # Remove any tag that has "?" as revision number. A "?" as revision number indicates, that this tag is from + # another series. + tags = [tag for tag in tags if tag.split()[-1].strip() != '?'] + # Get the last tag and split it in a revision and tag name. + tag_version, tag_revision = tags[-1].split() + # If they are equal, then this tree is tarball with the source for the release. We do not want the revision + # number in the full version. + if tree_revision == tag_revision: + full_version = tag_version.strip() + else: + full_version = '%s-bzr%s' % (tag_version.strip(), tree_revision.strip()) + else: + # We're not running the development version, let's use the file. + file_path = AppLocation.get_directory(AppLocation.VersionDir) + file_path = os.path.join(file_path, '.version') + version_file = None + try: + version_file = open(file_path, 'r') + full_version = str(version_file.read()).rstrip() + except IOError: + log.exception('Error in version file.') + full_version = '0.0.0-bzr000' + finally: + if version_file: + version_file.close() + bits = full_version.split('-') + APPLICATION_VERSION = { + 'full': full_version, + 'version': bits[0], + 'build': bits[1] if len(bits) > 1 else None + } + if APPLICATION_VERSION['build']: + log.info('Openlp version %s build %s', APPLICATION_VERSION['version'], APPLICATION_VERSION['build']) + else: + log.info('Openlp version %s' % APPLICATION_VERSION['version']) + return APPLICATION_VERSION + + +def check_latest_version(current_version): + """ + Check the latest version of OpenLP against the version file on the OpenLP + site. + + **Rules around versions and version files:** + + * If a version number has a build (i.e. -bzr1234), then it is a nightly. + * If a version number's minor version is an odd number, it is a development release. + * If a version number's minor version is an even number, it is a stable release. + + :param current_version: The current version of OpenLP. + """ + version_string = current_version['full'] + # set to prod in the distribution config file. + settings = Settings() + settings.beginGroup('core') + last_test = settings.value('last version test') + this_test = str(datetime.now().date()) + settings.setValue('last version test', this_test) + settings.endGroup() + if last_test != this_test: + if current_version['build']: + req = urllib.request.Request('http://www.openlp.org/files/nightly_version.txt') + else: + version_parts = current_version['version'].split('.') + if int(version_parts[1]) % 2 != 0: + req = urllib.request.Request('http://www.openlp.org/files/dev_version.txt') + else: + req = urllib.request.Request('http://www.openlp.org/files/version.txt') + req.add_header('User-Agent', 'OpenLP/%s %s/%s; ' % (current_version['full'], platform.system(), + platform.release())) + remote_version = None + retries = 0 + while True: + try: + remote_version = str(urllib.request.urlopen(req, None, + timeout=CONNECTION_TIMEOUT).read().decode()).strip() + except (urllib.error.URLError, ConnectionError): + if retries > CONNECTION_RETRIES: + log.exception('Failed to download the latest OpenLP version file') + else: + retries += 1 + time.sleep(0.1) + continue + break + if remote_version: + version_string = remote_version + return version_string + diff --git a/openlp/core/lib/plugin.py b/openlp/core/lib/plugin.py index 6c19ac1dd..b9c8ca5f0 100644 --- a/openlp/core/lib/plugin.py +++ b/openlp/core/lib/plugin.py @@ -24,11 +24,10 @@ Provide the generic plugin functionality for OpenLP plugins. """ import logging - from PyQt5 import QtCore from openlp.core.common import Registry, RegistryProperties, Settings, UiStrings -from openlp.core.utils import get_application_version +from openlp.core.common.versionchecker import get_application_version log = logging.getLogger(__name__) diff --git a/openlp/core/ui/aboutform.py b/openlp/core/ui/aboutform.py index b376d4646..fc29f968c 100644 --- a/openlp/core/ui/aboutform.py +++ b/openlp/core/ui/aboutform.py @@ -26,8 +26,8 @@ import webbrowser from PyQt5 import QtCore, QtWidgets +from openlp.core.common.versionchecker import get_application_version from openlp.core.lib import translate -from openlp.core.utils import get_application_version from .aboutdialog import UiAboutDialog diff --git a/openlp/core/ui/exceptionform.py b/openlp/core/ui/exceptionform.py index 2e4661579..68dd9705f 100644 --- a/openlp/core/ui/exceptionform.py +++ b/openlp/core/ui/exceptionform.py @@ -23,18 +23,17 @@ The actual exception dialog form. """ import logging -import re import os import platform +import re import bs4 import sqlalchemy +from PyQt5 import Qt, QtCore, QtGui, QtWebKit, QtWidgets from lxml import etree from openlp.core.common import RegistryProperties, is_linux -from PyQt5 import Qt, QtCore, QtGui, QtWebKit, QtWidgets - try: import migrate MIGRATE_VERSION = getattr(migrate, '__version__', '< 0.7') @@ -74,7 +73,7 @@ except ImportError: VLC_VERSION = '-' from openlp.core.common import Settings, UiStrings, translate -from openlp.core.utils import get_application_version +from openlp.core.common.versionchecker import get_application_version from .exceptiondialog import Ui_ExceptionDialog diff --git a/openlp/core/ui/mainwindow.py b/openlp/core/ui/mainwindow.py index 9618b1c48..228969ad1 100644 --- a/openlp/core/ui/mainwindow.py +++ b/openlp/core/ui/mainwindow.py @@ -37,6 +37,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets from openlp.core.common import Registry, RegistryProperties, AppLocation, LanguageManager, Settings, \ check_directory_exists, translate, is_win, is_macosx, add_actions from openlp.core.common.actions import ActionList, CategoryOrder +from openlp.core.common.versionchecker import get_application_version from openlp.core.lib import Renderer, OpenLPDockWidget, PluginManager, ImageManager, PluginStatus, ScreenList, \ build_icon from openlp.core.lib.ui import UiStrings, create_action @@ -46,7 +47,6 @@ from openlp.core.ui.firsttimeform import FirstTimeForm from openlp.core.ui.media import MediaController from openlp.core.ui.printserviceform import PrintServiceForm from openlp.core.ui.projector.manager import ProjectorManager -from openlp.core.utils import get_application_version log = logging.getLogger(__name__) diff --git a/openlp/core/utils/__init__.py b/openlp/core/utils/__init__.py index 3cd1b0295..656c9f1cb 100644 --- a/openlp/core/utils/__init__.py +++ b/openlp/core/utils/__init__.py @@ -24,7 +24,6 @@ The :mod:`openlp.core.utils` module provides the utility libraries for OpenLP. """ import logging import os -import platform import re import socket import sys @@ -32,8 +31,6 @@ import time import urllib.error import urllib.parse import urllib.request -from datetime import datetime -from distutils.version import LooseVersion from http.client import HTTPException from random import randint from shutil import which @@ -90,34 +87,6 @@ CONNECTION_TIMEOUT = 30 CONNECTION_RETRIES = 2 -class VersionThread(QtCore.QThread): - """ - A special Qt thread class to fetch the version of OpenLP from the website. - This is threaded so that it doesn't affect the loading time of OpenLP. - """ - def __init__(self, main_window): - """ - Constructor for the thread class. - - :param main_window: The main window Object. - """ - log.debug("VersionThread - Initialise") - super(VersionThread, self).__init__(None) - self.main_window = main_window - - def run(self): - """ - Run the thread. - """ - self.sleep(1) - log.debug('Version thread - run') - app_version = get_application_version() - version = check_latest_version(app_version) - log.debug("Versions %s and %s " % (LooseVersion(str(version)), LooseVersion(str(app_version['full'])))) - if LooseVersion(str(version)) > LooseVersion(str(app_version['full'])): - self.main_window.openlp_version_check.emit('%s' % version) - - class HTTPRedirectHandlerFixed(urllib.request.HTTPRedirectHandler): """ Special HTTPRedirectHandler used to work around http://bugs.python.org/issue22248 @@ -145,127 +114,6 @@ class HTTPRedirectHandlerFixed(urllib.request.HTTPRedirectHandler): return super(HTTPRedirectHandlerFixed, self).redirect_request(req, fp, code, msg, headers, fixed_url) -def get_application_version(): - """ - Returns the application version of the running instance of OpenLP:: - - {'full': '1.9.4-bzr1249', 'version': '1.9.4', 'build': 'bzr1249'} - """ - global APPLICATION_VERSION - if APPLICATION_VERSION: - return APPLICATION_VERSION - if '--dev-version' in sys.argv or '-d' in sys.argv: - # NOTE: The following code is a duplicate of the code in setup.py. Any fix applied here should also be applied - # there. - - # Get the revision of this tree. - bzr = Popen(('bzr', 'revno'), stdout=PIPE) - tree_revision, error = bzr.communicate() - tree_revision = tree_revision.decode() - code = bzr.wait() - if code != 0: - raise Exception('Error running bzr log') - - # Get all tags. - bzr = Popen(('bzr', 'tags'), stdout=PIPE) - output, error = bzr.communicate() - code = bzr.wait() - if code != 0: - raise Exception('Error running bzr tags') - tags = list(map(bytes.decode, output.splitlines())) - if not tags: - tag_version = '0.0.0' - tag_revision = '0' - else: - # Remove any tag that has "?" as revision number. A "?" as revision number indicates, that this tag is from - # another series. - tags = [tag for tag in tags if tag.split()[-1].strip() != '?'] - # Get the last tag and split it in a revision and tag name. - tag_version, tag_revision = tags[-1].split() - # If they are equal, then this tree is tarball with the source for the release. We do not want the revision - # number in the full version. - if tree_revision == tag_revision: - full_version = tag_version.strip() - else: - full_version = '%s-bzr%s' % (tag_version.strip(), tree_revision.strip()) - else: - # We're not running the development version, let's use the file. - file_path = AppLocation.get_directory(AppLocation.VersionDir) - file_path = os.path.join(file_path, '.version') - version_file = None - try: - version_file = open(file_path, 'r') - full_version = str(version_file.read()).rstrip() - except IOError: - log.exception('Error in version file.') - full_version = '0.0.0-bzr000' - finally: - if version_file: - version_file.close() - bits = full_version.split('-') - APPLICATION_VERSION = { - 'full': full_version, - 'version': bits[0], - 'build': bits[1] if len(bits) > 1 else None - } - if APPLICATION_VERSION['build']: - log.info('Openlp version %s build %s', APPLICATION_VERSION['version'], APPLICATION_VERSION['build']) - else: - log.info('Openlp version %s' % APPLICATION_VERSION['version']) - return APPLICATION_VERSION - - -def check_latest_version(current_version): - """ - Check the latest version of OpenLP against the version file on the OpenLP - site. - - **Rules around versions and version files:** - - * If a version number has a build (i.e. -bzr1234), then it is a nightly. - * If a version number's minor version is an odd number, it is a development release. - * If a version number's minor version is an even number, it is a stable release. - - :param current_version: The current version of OpenLP. - """ - version_string = current_version['full'] - # set to prod in the distribution config file. - settings = Settings() - settings.beginGroup('core') - last_test = settings.value('last version test') - this_test = str(datetime.now().date()) - settings.setValue('last version test', this_test) - settings.endGroup() - if last_test != this_test: - if current_version['build']: - req = urllib.request.Request('http://www.openlp.org/files/nightly_version.txt') - else: - version_parts = current_version['version'].split('.') - if int(version_parts[1]) % 2 != 0: - req = urllib.request.Request('http://www.openlp.org/files/dev_version.txt') - else: - req = urllib.request.Request('http://www.openlp.org/files/version.txt') - req.add_header('User-Agent', 'OpenLP/%s %s/%s; ' % (current_version['full'], platform.system(), - platform.release())) - remote_version = None - retries = 0 - while True: - try: - remote_version = str(urllib.request.urlopen(req, None, - timeout=CONNECTION_TIMEOUT).read().decode()).strip() - except (urllib.error.URLError, ConnectionError): - if retries > CONNECTION_RETRIES: - log.exception('Failed to download the latest OpenLP version file') - else: - retries += 1 - time.sleep(0.1) - continue - break - if remote_version: - version_string = remote_version - return version_string - - def get_filesystem_encoding(): """ Returns the name of the encoding used to convert Unicode filenames into system file names. diff --git a/openlp/plugins/songs/lib/openlyricsxml.py b/openlp/plugins/songs/lib/openlyricsxml.py index bba60baa2..806df438d 100644 --- a/openlp/plugins/songs/lib/openlyricsxml.py +++ b/openlp/plugins/songs/lib/openlyricsxml.py @@ -62,10 +62,10 @@ import re from lxml import etree, objectify from openlp.core.common import translate +from openlp.core.common.versionchecker import get_application_version from openlp.core.lib import FormattingTags from openlp.plugins.songs.lib import VerseType, clean_song from openlp.plugins.songs.lib.db import Author, AuthorType, Book, Song, Topic -from openlp.core.utils import get_application_version log = logging.getLogger(__name__) diff --git a/tests/functional/openlp_core_common/test_versionchecker.py b/tests/functional/openlp_core_common/test_versionchecker.py new file mode 100644 index 000000000..ab2269667 --- /dev/null +++ b/tests/functional/openlp_core_common/test_versionchecker.py @@ -0,0 +1,63 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2016 OpenLP Developers # +# --------------------------------------------------------------------------- # +# This program is free software; you can redistribute it and/or modify it # +# under the terms of the GNU General Public License as published by the Free # +# Software Foundation; version 2 of the License. # +# # +# This program is distributed in the hope that it will be useful, but WITHOUT # +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # +# more details. # +# # +# You should have received a copy of the GNU General Public License along # +# with this program; if not, write to the Free Software Foundation, Inc., 59 # +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # +############################################################################### +""" +Package to test the openlp.core.common.versionchecker package. +""" +from unittest import TestCase + +from openlp.core.common.settings import Settings +from openlp.core.common.versionchecker import VersionThread +from tests.functional import MagicMock, patch +from tests.helpers.testmixin import TestMixin + + +class TestVersionchecker(TestMixin, TestCase): + + def setUp(self): + """ + Create an instance and a few example actions. + """ + self.build_settings() + + def tearDown(self): + """ + Clean up + """ + self.destroy_settings() + + def version_thread_triggered_test(self): + """ + Test the version thread call does not trigger UI + :return: + """ + # GIVEN: a equal version setup and the data is not today. + mocked_main_window = MagicMock() + Settings().setValue('core/last version test', '1950-04-01') + # WHEN: We check to see if the version is different . + with patch('PyQt5.QtCore.QThread'),\ + patch('openlp.core.common.versionchecker.get_application_version') as mocked_get_application_version: + mocked_get_application_version.return_value = {'version': '1.0.0', 'build': '', 'full': '2.0.4'} + version_thread = VersionThread(mocked_main_window) + version_thread.run() + # THEN: If the version has changed the main window is notified + self.assertTrue(mocked_main_window.openlp_version_check.emit.called, + 'The main windows should have been notified') \ No newline at end of file diff --git a/tests/functional/openlp_core_utils/test_init.py b/tests/functional/openlp_core_utils/test_init.py index 6a62f3a7f..d01c9f300 100644 --- a/tests/functional/openlp_core_utils/test_init.py +++ b/tests/functional/openlp_core_utils/test_init.py @@ -24,9 +24,8 @@ Package to test the openlp.core.utils.actions package. """ from unittest import TestCase -from openlp.core.common.settings import Settings -from openlp.core.utils import VersionThread, get_uno_command -from tests.functional import MagicMock, patch +from openlp.core.utils import get_uno_command +from tests.functional import patch from tests.helpers.testmixin import TestMixin @@ -44,23 +43,6 @@ class TestInitFunctions(TestMixin, TestCase): """ self.destroy_settings() - def version_thread_triggered_test(self): - """ - Test the version thread call does not trigger UI - :return: - """ - # GIVEN: a equal version setup and the data is not today. - mocked_main_window = MagicMock() - Settings().setValue('core/last version test', '1950-04-01') - # WHEN: We check to see if the version is different . - with patch('PyQt5.QtCore.QThread'),\ - patch('openlp.core.utils.get_application_version') as mocked_get_application_version: - mocked_get_application_version.return_value = {'version': '1.0.0', 'build': '', 'full': '2.0.4'} - version_thread = VersionThread(mocked_main_window) - version_thread.run() - # THEN: If the version has changed the main window is notified - self.assertTrue(mocked_main_window.openlp_version_check.emit.called, - 'The main windows should have been notified') def get_uno_command_libreoffice_command_exists_test(self): """ diff --git a/tests/functional/openlp_core_utils/test_utils.py b/tests/functional/openlp_core_utils/test_utils.py index 0c4234a3d..28d890a22 100644 --- a/tests/functional/openlp_core_utils/test_utils.py +++ b/tests/functional/openlp_core_utils/test_utils.py @@ -27,7 +27,6 @@ from unittest import TestCase from openlp.core.utils import clean_filename, delete_file, get_filesystem_encoding, \ split_filename, _get_user_agent, get_web_page, get_uno_instance -from openlp.core.common.languagemanager import get_locale_key, get_natural_key from tests.functional import MagicMock, patch