diff --git a/openlp/core/api/endpoint/pluginhelpers.py b/openlp/core/api/endpoint/pluginhelpers.py index a74fa524a..75dc8e8e6 100644 --- a/openlp/core/api/endpoint/pluginhelpers.py +++ b/openlp/core/api/endpoint/pluginhelpers.py @@ -115,7 +115,7 @@ def display_thumbnails(request, controller_name, log, dimensions, file_name, sli height = -1 image = None if dimensions: - match = re.search('(\d+)x(\d+)', dimensions) + match = re.search(r'(\d+)x(\d+)', dimensions) if match: # let's make sure that the dimensions are within reason width = sorted([10, int(match.group(1)), 1000])[1] diff --git a/openlp/core/api/http/wsgiapp.py b/openlp/core/api/http/wsgiapp.py index 48364f340..aa90a28aa 100644 --- a/openlp/core/api/http/wsgiapp.py +++ b/openlp/core/api/http/wsgiapp.py @@ -39,7 +39,7 @@ log = logging.getLogger(__name__) def _route_to_regex(route): - """ + r""" Convert a route to a regular expression For example: diff --git a/openlp/core/api/tab.py b/openlp/core/api/tab.py index 7103dc6cf..b0477a282 100644 --- a/openlp/core/api/tab.py +++ b/openlp/core/api/tab.py @@ -54,7 +54,7 @@ class ApiTab(SettingsTab): self.address_label.setObjectName('address_label') self.address_edit = QtWidgets.QLineEdit(self.server_settings_group_box) self.address_edit.setSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) - self.address_edit.setValidator(QtGui.QRegExpValidator(QtCore.QRegExp('\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}'), + self.address_edit.setValidator(QtGui.QRegExpValidator(QtCore.QRegExp(r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}'), self)) self.address_edit.setObjectName('address_edit') self.server_settings_layout.addRow(self.address_label, self.address_edit) diff --git a/openlp/core/common/__init__.py b/openlp/core/common/__init__.py index a6e7620b5..0148bce99 100644 --- a/openlp/core/common/__init__.py +++ b/openlp/core/common/__init__.py @@ -64,13 +64,17 @@ def get_local_ip4(): log.debug('Getting local IPv4 interface(es) information') my_ip4 = {} for iface in QNetworkInterface.allInterfaces(): + log.debug('Checking for isValid and flags == IsUP | IsRunning') if not iface.isValid() or not (iface.flags() & (QNetworkInterface.IsUp | QNetworkInterface.IsRunning)): continue + 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') my_ip4[iface.name()] = {'ip': ip.toString(), 'broadcast': address.broadcast().toString(), 'netmask': address.netmask().toString(), @@ -79,14 +83,21 @@ def get_local_ip4(): ip.toIPv4Address()).toString() } log.debug('Adding {iface} to active list'.format(iface=iface.name())) + 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 log.warning('No active IPv4 interfaces found except localhost') else: # Since we have a valid IP4 interface, remove localhost - log.debug('Found at least one IPv4 interface, removing localhost') - my_ip4.pop('lo') + if 'lo' in my_ip4: + log.debug('Found at least one IPv4 interface, removing localhost') + my_ip4.pop('lo') return my_ip4 diff --git a/openlp/core/common/httputils.py b/openlp/core/common/httputils.py index e7d11fad5..54173a8d2 100644 --- a/openlp/core/common/httputils.py +++ b/openlp/core/common/httputils.py @@ -32,6 +32,7 @@ import requests from openlp.core.common import trace_error_handler from openlp.core.common.registry import Registry +from openlp.core.common.settings import ProxyMode, Settings log = logging.getLogger(__name__ + '.__init__') @@ -64,6 +65,39 @@ CONNECTION_TIMEOUT = 30 CONNECTION_RETRIES = 2 +def get_proxy_settings(mode=None): + """ + Create a dictionary containing the proxy settings. + + :param ProxyMode | None mode: Specify the source of the proxy settings + :return: A dict using the format expected by the requests library. + :rtype: dict | None + """ + settings = Settings() + if mode is None: + mode = settings.value('advanced/proxy mode') + if mode == ProxyMode.NO_PROXY: + return {'http': None, 'https': None} + elif mode == ProxyMode.SYSTEM_PROXY: + # The requests library defaults to using the proxy settings in the environment variables + return + elif mode == ProxyMode.MANUAL_PROXY: + http_addr = settings.value('advanced/proxy http') + https_addr = settings.value('advanced/proxy https') + username = settings.value('advanced/proxy username') + password = settings.value('advanced/proxy password') + basic_auth = '' + if username: + basic_auth = '{username}:{password}@'.format(username=username, password=password) + http_value = None + https_value = None + if http_addr: + http_value = 'http://{basic_auth}{http_addr}'.format(basic_auth=basic_auth, http_addr=http_addr) + if https_addr: + https_value = 'https://{basic_auth}{https_addr}'.format(basic_auth=basic_auth, https_addr=https_addr) + return {'http': http_value, 'https': https_value} + + def get_user_agent(): """ Return a user agent customised for the platform the user is on. @@ -75,14 +109,15 @@ def get_user_agent(): return browser_list[random_index] -def get_web_page(url, headers=None, update_openlp=False, proxies=None): +def get_web_page(url, headers=None, update_openlp=False, proxy=None): """ Attempts to download the webpage at url and returns that page or None. :param url: The URL to be downloaded. - :param header: An optional HTTP header to pass in the request to the web server. - :param update_openlp: Tells OpenLP to update itself if the page is successfully downloaded. - Defaults to False. + :param dict | None headers: An optional HTTP header to pass in the request to the web server. + :param update_openlp: Tells OpenLP to update itself if the page is successfully downloaded. Defaults to False. + :param dict | ProxyMode | None proxy: ProxyMode enum or a dictionary containing the proxy servers, with their types + as the key e.g. {'http': 'http://proxyserver:port', 'https': 'https://proxyserver:port'} """ if not url: return None @@ -90,11 +125,13 @@ def get_web_page(url, headers=None, update_openlp=False, proxies=None): headers = {} if 'user-agent' not in [key.lower() for key in headers.keys()]: headers['User-Agent'] = get_user_agent() + if not isinstance(proxy, dict): + proxy = get_proxy_settings(mode=proxy) log.debug('Downloading URL = %s' % url) retries = 0 while retries < CONNECTION_RETRIES: try: - response = requests.get(url, headers=headers, proxies=proxies, timeout=float(CONNECTION_TIMEOUT)) + response = requests.get(url, headers=headers, proxies=proxy, timeout=float(CONNECTION_TIMEOUT)) log.debug('Downloaded page {url}'.format(url=response.url)) break except OSError: diff --git a/openlp/core/common/settings.py b/openlp/core/common/settings.py index 91a587616..74d686b09 100644 --- a/openlp/core/common/settings.py +++ b/openlp/core/common/settings.py @@ -26,6 +26,7 @@ import datetime import json import logging import os +from enum import IntEnum from tempfile import gettempdir from PyQt5 import QtCore, QtGui @@ -38,6 +39,13 @@ log = logging.getLogger(__name__) __version__ = 2 + +class ProxyMode(IntEnum): + NO_PROXY = 1 + SYSTEM_PROXY = 2 + MANUAL_PROXY = 3 + + # Fix for bug #1014422. X11_BYPASS_DEFAULT = True if is_linux(): # pragma: no cover @@ -116,6 +124,11 @@ class Settings(QtCore.QSettings): 'advanced/print file meta data': False, 'advanced/print notes': False, 'advanced/print slide text': False, + 'advanced/proxy mode': ProxyMode.SYSTEM_PROXY, + 'advanced/proxy http': '', + 'advanced/proxy https': '', + 'advanced/proxy username': '', + 'advanced/proxy password': '', 'advanced/recent file count': 4, 'advanced/save current plugin': False, 'advanced/slide limits': SlideLimits.End, diff --git a/openlp/core/lib/__init__.py b/openlp/core/lib/__init__.py index bcc87c19e..c4aa92702 100644 --- a/openlp/core/lib/__init__.py +++ b/openlp/core/lib/__init__.py @@ -415,7 +415,7 @@ def expand_chords(text): chords_on_prev_line = True # Matches a chord, a tail, a remainder and a line end. See expand_and_align_chords_in_line() for more info. new_line += re.sub(r'\[(.*?)\]([\u0080-\uFFFF,\w]*)' - '([\u0080-\uFFFF,\w,\s,\.,\,,\!,\?,\;,\:,\|,\",\',\-,\_]*)(\Z)?', + r'([\u0080-\uFFFF,\w,\s,\.,\,,\!,\?,\;,\:,\|,\",\',\-,\_]*)(\Z)?', expand_and_align_chords_in_line, line) new_line += '' expanded_text_lines.append(new_line) diff --git a/openlp/core/lib/htmlbuilder.py b/openlp/core/lib/htmlbuilder.py index f6a0c6e61..5ef94288c 100644 --- a/openlp/core/lib/htmlbuilder.py +++ b/openlp/core/lib/htmlbuilder.py @@ -19,7 +19,7 @@ # with this program; if not, write to the Free Software Foundation, Inc., 59 # # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### -""" +r""" This module is responsible for generating the HTML for :class:`~openlp.core.ui.maindisplay`. The ``build_html`` function is the function which has to be called from outside. The generated and returned HTML will look similar to this:: @@ -416,7 +416,7 @@ from openlp.core.lib.theme import BackgroundType, BackgroundGradientType, Vertic log = logging.getLogger(__name__) -HTML_SRC = Template(""" +HTML_SRC = Template(r""" diff --git a/openlp/core/projectors/pjlink.py b/openlp/core/projectors/pjlink.py index b2b23a188..3ea1bb414 100644 --- a/openlp/core/projectors/pjlink.py +++ b/openlp/core/projectors/pjlink.py @@ -338,7 +338,7 @@ class PJLinkCommands(object): # Due to stupid projectors not following standards (Optoma, BenQ comes to mind), # AND the different responses that can be received, the semi-permanent way to # fix the class reply is to just remove all non-digit characters. - chk = re.findall('\d', data) + chk = re.findall(r'\d', data) if len(chk) < 1: log.error('({ip}) No numbers found in class version reply "{data}" - ' 'defaulting to class "1"'.format(ip=self.entry.name, data=data)) diff --git a/openlp/core/ui/advancedtab.py b/openlp/core/ui/advancedtab.py index d0fa59bf1..81fdd409b 100644 --- a/openlp/core/ui/advancedtab.py +++ b/openlp/core/ui/advancedtab.py @@ -36,6 +36,7 @@ from openlp.core.ui.style import HAS_DARK_STYLE from openlp.core.ui.icons import UiIcons from openlp.core.widgets.edits import PathEdit from openlp.core.widgets.enums import PathEditType +from openlp.core.widgets.widgets import ProxyWidget log = logging.getLogger(__name__) @@ -77,6 +78,9 @@ class AdvancedTab(SettingsTab): self.media_plugin_check_box = QtWidgets.QCheckBox(self.ui_group_box) self.media_plugin_check_box.setObjectName('media_plugin_check_box') self.ui_layout.addRow(self.media_plugin_check_box) + self.hide_mouse_check_box = QtWidgets.QCheckBox(self.ui_group_box) + self.hide_mouse_check_box.setObjectName('hide_mouse_check_box') + self.ui_layout.addWidget(self.hide_mouse_check_box) self.double_click_live_check_box = QtWidgets.QCheckBox(self.ui_group_box) self.double_click_live_check_box.setObjectName('double_click_live_check_box') self.ui_layout.addRow(self.double_click_live_check_box) @@ -117,6 +121,24 @@ class AdvancedTab(SettingsTab): self.use_dark_style_checkbox = QtWidgets.QCheckBox(self.ui_group_box) self.use_dark_style_checkbox.setObjectName('use_dark_style_checkbox') self.ui_layout.addRow(self.use_dark_style_checkbox) + # Service Item Slide Limits + self.slide_group_box = QtWidgets.QGroupBox(self.left_column) + self.slide_group_box.setObjectName('slide_group_box') + self.slide_layout = QtWidgets.QVBoxLayout(self.slide_group_box) + self.slide_layout.setObjectName('slide_layout') + self.slide_label = QtWidgets.QLabel(self.slide_group_box) + self.slide_label.setWordWrap(True) + self.slide_layout.addWidget(self.slide_label) + self.end_slide_radio_button = QtWidgets.QRadioButton(self.slide_group_box) + self.end_slide_radio_button.setObjectName('end_slide_radio_button') + self.slide_layout.addWidget(self.end_slide_radio_button) + self.wrap_slide_radio_button = QtWidgets.QRadioButton(self.slide_group_box) + self.wrap_slide_radio_button.setObjectName('wrap_slide_radio_button') + self.slide_layout.addWidget(self.wrap_slide_radio_button) + self.next_item_radio_button = QtWidgets.QRadioButton(self.slide_group_box) + self.next_item_radio_button.setObjectName('next_item_radio_button') + self.slide_layout.addWidget(self.next_item_radio_button) + self.left_layout.addWidget(self.slide_group_box) # Data Directory self.data_directory_group_box = QtWidgets.QGroupBox(self.left_column) self.data_directory_group_box.setObjectName('data_directory_group_box') @@ -143,33 +165,6 @@ class AdvancedTab(SettingsTab): self.data_directory_layout.addRow(self.data_directory_copy_check_layout) self.data_directory_layout.addRow(self.new_data_directory_has_files_label) self.left_layout.addWidget(self.data_directory_group_box) - # Hide mouse - self.hide_mouse_group_box = QtWidgets.QGroupBox(self.right_column) - self.hide_mouse_group_box.setObjectName('hide_mouse_group_box') - self.hide_mouse_layout = QtWidgets.QVBoxLayout(self.hide_mouse_group_box) - self.hide_mouse_layout.setObjectName('hide_mouse_layout') - self.hide_mouse_check_box = QtWidgets.QCheckBox(self.hide_mouse_group_box) - self.hide_mouse_check_box.setObjectName('hide_mouse_check_box') - self.hide_mouse_layout.addWidget(self.hide_mouse_check_box) - self.right_layout.addWidget(self.hide_mouse_group_box) - # Service Item Slide Limits - self.slide_group_box = QtWidgets.QGroupBox(self.right_column) - self.slide_group_box.setObjectName('slide_group_box') - self.slide_layout = QtWidgets.QVBoxLayout(self.slide_group_box) - self.slide_layout.setObjectName('slide_layout') - self.slide_label = QtWidgets.QLabel(self.slide_group_box) - self.slide_label.setWordWrap(True) - self.slide_layout.addWidget(self.slide_label) - self.end_slide_radio_button = QtWidgets.QRadioButton(self.slide_group_box) - self.end_slide_radio_button.setObjectName('end_slide_radio_button') - self.slide_layout.addWidget(self.end_slide_radio_button) - self.wrap_slide_radio_button = QtWidgets.QRadioButton(self.slide_group_box) - self.wrap_slide_radio_button.setObjectName('wrap_slide_radio_button') - self.slide_layout.addWidget(self.wrap_slide_radio_button) - self.next_item_radio_button = QtWidgets.QRadioButton(self.slide_group_box) - self.next_item_radio_button.setObjectName('next_item_radio_button') - self.slide_layout.addWidget(self.next_item_radio_button) - self.right_layout.addWidget(self.slide_group_box) # Display Workarounds self.display_workaround_group_box = QtWidgets.QGroupBox(self.right_column) self.display_workaround_group_box.setObjectName('display_workaround_group_box') @@ -224,6 +219,9 @@ class AdvancedTab(SettingsTab): self.service_name_example.setObjectName('service_name_example') self.service_name_layout.addRow(self.service_name_example_label, self.service_name_example) self.right_layout.addWidget(self.service_name_group_box) + # Proxies + self.proxy_widget = ProxyWidget(self.right_column) + self.right_layout.addWidget(self.proxy_widget) # After the last item on each side, add some spacing self.left_layout.addStretch() self.right_layout.addStretch() @@ -312,7 +310,6 @@ class AdvancedTab(SettingsTab): translate('OpenLP.AdvancedTab', 'Revert to the default service name "{name}".').format(name=UiStrings().DefaultServiceName)) self.service_name_example_label.setText(translate('OpenLP.AdvancedTab', 'Example:')) - self.hide_mouse_group_box.setTitle(translate('OpenLP.AdvancedTab', 'Mouse Cursor')) self.hide_mouse_check_box.setText(translate('OpenLP.AdvancedTab', 'Hide mouse cursor when over display window')) self.data_directory_new_label.setText(translate('OpenLP.AdvancedTab', 'Path:')) self.data_directory_cancel_button.setText(translate('OpenLP.AdvancedTab', 'Cancel')) @@ -335,6 +332,7 @@ class AdvancedTab(SettingsTab): self.wrap_slide_radio_button.setText(translate('OpenLP.GeneralTab', '&Wrap around')) self.next_item_radio_button.setText(translate('OpenLP.GeneralTab', '&Move to next/previous service item')) self.search_as_type_check_box.setText(translate('SongsPlugin.GeneralTab', 'Enable search as you type')) + self.proxy_widget.retranslate_ui() def load(self): """ @@ -437,6 +435,7 @@ class AdvancedTab(SettingsTab): if HAS_DARK_STYLE: settings.setValue('use_dark_style', self.use_dark_style_checkbox.isChecked()) settings.endGroup() + self.proxy_widget.save() def on_search_as_type_check_box_changed(self, check_state): self.is_search_as_you_type_enabled = (check_state == QtCore.Qt.Checked) diff --git a/openlp/core/ui/media/__init__.py b/openlp/core/ui/media/__init__.py index da80aee5f..976e0b744 100644 --- a/openlp/core/ui/media/__init__.py +++ b/openlp/core/ui/media/__init__.py @@ -80,7 +80,7 @@ def get_media_players(): """ log.debug('get_media_players') saved_players = Settings().value('media/players') - reg_ex = QtCore.QRegExp(".*\[(.*)\].*") + reg_ex = QtCore.QRegExp(r'.*\[(.*)\].*') if Settings().value('media/override player') == QtCore.Qt.Checked: if reg_ex.exactMatch(saved_players): overridden_player = '{text}'.format(text=reg_ex.cap(1)) diff --git a/openlp/core/ui/slidecontroller.py b/openlp/core/ui/slidecontroller.py index ad949e74a..a07961f23 100644 --- a/openlp/core/ui/slidecontroller.py +++ b/openlp/core/ui/slidecontroller.py @@ -1127,7 +1127,7 @@ class SlideController(DisplayController, LogMixin, RegistryProperties): # done by the thread holding the lock. If it is a "start" slide, we must wait for the lock, but only for 0.2 # seconds, since we don't want to cause a deadlock timeout = 0.2 if start else -1 - if not self.slide_selected_lock.acquire(start, timeout): + if not self.slide_selected_lock.acquire(start, timeout): # pylint: disable=too-many-function-args if start: self.log_debug('Could not get lock in slide_selected after waiting %f, skip to avoid deadlock.' % timeout) diff --git a/openlp/core/widgets/widgets.py b/openlp/core/widgets/widgets.py new file mode 100644 index 000000000..052057eff --- /dev/null +++ b/openlp/core/widgets/widgets.py @@ -0,0 +1,132 @@ +# -*- 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 # +############################################################################### +""" +The :mod:`~openlp.core.widgets.widgets` module contains custom widgets used in OpenLP +""" +from PyQt5 import QtWidgets + +from openlp.core.common.i18n import translate +from openlp.core.common.settings import ProxyMode, Settings + + +class ProxyWidget(QtWidgets.QGroupBox): + """ + A proxy settings widget that implements loading and saving its settings. + """ + def __init__(self, parent=None): + """ + Initialise the widget. + + :param QtWidgets.QWidget | None parent: The widgets parent + """ + super().__init__(parent) + self._setup() + + def _setup(self): + """ + A setup method seperate from __init__ to allow easier testing + """ + self.setup_ui() + self.load() + + def setup_ui(self): + """ + Create the widget layout and sub widgets + """ + self.layout = QtWidgets.QFormLayout(self) + self.radio_group = QtWidgets.QButtonGroup(self) + self.no_proxy_radio = QtWidgets.QRadioButton('', self) + self.radio_group.addButton(self.no_proxy_radio, ProxyMode.NO_PROXY) + self.layout.setWidget(0, QtWidgets.QFormLayout.SpanningRole, self.no_proxy_radio) + self.use_sysem_proxy_radio = QtWidgets.QRadioButton('', self) + self.radio_group.addButton(self.use_sysem_proxy_radio, ProxyMode.SYSTEM_PROXY) + self.layout.setWidget(1, QtWidgets.QFormLayout.SpanningRole, self.use_sysem_proxy_radio) + self.manual_proxy_radio = QtWidgets.QRadioButton('', self) + self.radio_group.addButton(self.manual_proxy_radio, ProxyMode.MANUAL_PROXY) + self.layout.setWidget(2, QtWidgets.QFormLayout.SpanningRole, self.manual_proxy_radio) + self.http_edit = QtWidgets.QLineEdit(self) + self.layout.addRow('HTTP:', self.http_edit) + self.https_edit = QtWidgets.QLineEdit(self) + self.layout.addRow('HTTPS:', self.https_edit) + self.username_edit = QtWidgets.QLineEdit(self) + self.layout.addRow('Username:', self.username_edit) + self.password_edit = QtWidgets.QLineEdit(self) + self.password_edit.setEchoMode(QtWidgets.QLineEdit.Password) + self.layout.addRow('Password:', self.password_edit) + # Signal / Slots + self.radio_group.buttonToggled.connect(self.on_radio_group_button_toggled) + + def on_radio_group_button_toggled(self, button, checked): + """ + Handles the toggled signal on the radio buttons. The signal is emitted twice if a radio butting being toggled on + causes another radio button in the group to be toggled off. + + En/Disables the `Manual Proxy` line edits depending on the currently selected radio button + + :param QtWidgets.QRadioButton button: The button that has toggled + :param bool checked: The buttons new state + """ + id = self.radio_group.id(button) # The work around (see above comment) + enable_manual_edits = id == ProxyMode.MANUAL_PROXY and checked + self.http_edit.setEnabled(enable_manual_edits) + self.https_edit.setEnabled(enable_manual_edits) + self.username_edit.setEnabled(enable_manual_edits) + self.password_edit.setEnabled(enable_manual_edits) + + def retranslate_ui(self): + """ + Translate the Ui + """ + self.setTitle(translate('OpenLP.ProxyWidget', 'Proxy Server Settings')) + self.no_proxy_radio.setText(translate('OpenLP.ProxyWidget', 'No prox&y')) + self.use_sysem_proxy_radio.setText(translate('OpenLP.ProxyWidget', '&Use system proxy')) + self.manual_proxy_radio.setText(translate('OpenLP.ProxyWidget', '&Manual proxy configuration')) + proxy_example = translate('OpenLP.ProxyWidget', 'e.g. proxy_server_address:port_no') + self.layout.labelForField(self.http_edit).setText(translate('OpenLP.ProxyWidget', 'HTTP:')) + self.http_edit.setPlaceholderText(proxy_example) + self.layout.labelForField(self.https_edit).setText(translate('OpenLP.ProxyWidget', 'HTTPS:')) + self.https_edit.setPlaceholderText(proxy_example) + self.layout.labelForField(self.username_edit).setText(translate('OpenLP.ProxyWidget', 'Username:')) + self.layout.labelForField(self.password_edit).setText(translate('OpenLP.ProxyWidget', 'Password:')) + + def load(self): + """ + Load the data from the settings to the widget. + """ + settings = Settings() + checked_radio = self.radio_group.button(settings.value('advanced/proxy mode')) + checked_radio.setChecked(True) + self.http_edit.setText(settings.value('advanced/proxy http')) + self.https_edit.setText(settings.value('advanced/proxy https')) + self.username_edit.setText(settings.value('advanced/proxy username')) + self.password_edit.setText(settings.value('advanced/proxy password')) + + def save(self): + """ + Save the widget data to the settings + """ + settings = Settings() # TODO: Migrate from old system + settings.setValue('advanced/proxy mode', self.radio_group.checkedId()) + settings.setValue('advanced/proxy http', self.http_edit.text()) + settings.setValue('advanced/proxy https', self.https_edit.text()) + settings.setValue('advanced/proxy username', self.username_edit.text()) + settings.setValue('advanced/proxy password', self.password_edit.text()) diff --git a/openlp/plugins/bibles/forms/editbibleform.py b/openlp/plugins/bibles/forms/editbibleform.py index bc89827d2..086a65ceb 100644 --- a/openlp/plugins/bibles/forms/editbibleform.py +++ b/openlp/plugins/bibles/forms/editbibleform.py @@ -183,7 +183,7 @@ class EditBibleForm(QtWidgets.QDialog, Ui_EditBibleDialog, RegistryProperties): """ Validate a book. """ - book_regex = re.compile('[\d]*[^\d]+$') + book_regex = re.compile(r'[\d]*[^\d]+$') if not new_book_name: self.book_name_edit[abbreviation].setFocus() critical_error_message_box( diff --git a/openlp/plugins/bibles/lib/__init__.py b/openlp/plugins/bibles/lib/__init__.py index 73c1ca68d..b1a51bfd7 100644 --- a/openlp/plugins/bibles/lib/__init__.py +++ b/openlp/plugins/bibles/lib/__init__.py @@ -217,7 +217,7 @@ def update_reference_separators(): # add various Unicode alternatives source_string = source_string.replace('-', '(?:[-\u00AD\u2010\u2011\u2012\u2014\u2014\u2212\uFE63\uFF0D])') source_string = source_string.replace(',', '(?:[,\u201A])') - REFERENCE_SEPARATORS['sep_{role}'.format(role=role)] = '\s*(?:{source})\s*'.format(source=source_string) + REFERENCE_SEPARATORS['sep_{role}'.format(role=role)] = r'\s*(?:{source})\s*'.format(source=source_string) REFERENCE_SEPARATORS['sep_{role}_default'.format(role=role)] = default_separators[index] # verse range match: (:)?(-((:)?|end)?)? range_regex = '(?:(?P[0-9]+){sep_v})?' \ @@ -255,7 +255,7 @@ def get_reference_match(match_type): def parse_reference(reference, bible, language_selection, book_ref_id=False): - """ + r""" This is the next generation über-awesome function that takes a person's typed in string and converts it to a list of references to be queried from the Bible database files. diff --git a/openlp/plugins/bibles/lib/mediaitem.py b/openlp/plugins/bibles/lib/mediaitem.py index 4fd533507..291a26054 100755 --- a/openlp/plugins/bibles/lib/mediaitem.py +++ b/openlp/plugins/bibles/lib/mediaitem.py @@ -42,7 +42,7 @@ from openlp.plugins.bibles.lib import DisplayStyle, LayoutStyle, VerseReferenceL log = logging.getLogger(__name__) -VALID_TEXT_SEARCH = re.compile('\w\w\w') +VALID_TEXT_SEARCH = re.compile(r'\w\w\w') def get_reference_separators(): diff --git a/openlp/plugins/media/forms/mediaclipselectorform.py b/openlp/plugins/media/forms/mediaclipselectorform.py index 83d9763fc..cee38e3ee 100644 --- a/openlp/plugins/media/forms/mediaclipselectorform.py +++ b/openlp/plugins/media/forms/mediaclipselectorform.py @@ -207,7 +207,7 @@ class MediaClipSelectorForm(QtWidgets.QDialog, Ui_MediaClipSelector, RegistryPro # detect if we're dealing with a DVD or CD, so we use different loading approaches depending on the OS. if is_win(): # If the given path is in the format "D:\" or "D:", prefix it with "/" to make VLC happy - pattern = re.compile('^\w:\\\\*$') + pattern = re.compile(r'^\w:\\\\*$') if pattern.match(path): path = '/' + path self.vlc_media = self.vlc_instance.media_new_location('dvd://' + path) diff --git a/openlp/plugins/songs/forms/editsongform.py b/openlp/plugins/songs/forms/editsongform.py index 36b144aef..53aa443bb 100644 --- a/openlp/plugins/songs/forms/editsongform.py +++ b/openlp/plugins/songs/forms/editsongform.py @@ -105,7 +105,7 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties): self.topics_list_view.setSortingEnabled(False) self.topics_list_view.setAlternatingRowColors(True) self.audio_list_widget.setAlternatingRowColors(True) - self.find_verse_split = re.compile('---\[\]---\n') + self.find_verse_split = re.compile(r'---\[\]---\n') self.whitespace = re.compile(r'\W+') self.find_tags = re.compile(r'\{/?\w+\}') @@ -316,7 +316,7 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties): multiple.append(verse_tag) self.song.lyrics = str(sxml.extract_xml(), 'utf-8') for verse in multiple: - self.song.verse_order = re.sub('([' + verse.upper() + verse.lower() + '])(\W|$)', + self.song.verse_order = re.sub(r'([' + verse.upper() + verse.lower() + r'])(\W|$)', r'\g<1>1\2', self.song.verse_order) except: log.exception('Problem processing song Lyrics \n{xml}'.format(xml=sxml.dump_xml())) diff --git a/openlp/plugins/songs/lib/__init__.py b/openlp/plugins/songs/lib/__init__.py index 2578d0f92..c86d822fd 100644 --- a/openlp/plugins/songs/lib/__init__.py +++ b/openlp/plugins/songs/lib/__init__.py @@ -557,7 +557,7 @@ def transpose_lyrics(lyrics, transpose_value): :return: The transposed lyrics """ # Split text by verse delimiter - both normal and optional - verse_list = re.split('(---\[.+?:.+?\]---|\[---\])', lyrics) + verse_list = re.split(r'(---\[.+?:.+?\]---|\[---\])', lyrics) transposed_lyrics = '' notation = Settings().value('songs/chord notation') for verse in verse_list: @@ -580,7 +580,7 @@ def transpose_verse(verse_text, transpose_value, notation): if '[' not in verse_text: return verse_text # Split the lyrics based on chord tags - lyric_list = re.split('(\[|\]|/)', verse_text) + lyric_list = re.split(r'(\[|\]|/)', verse_text) transposed_lyrics = '' in_tag = False for word in lyric_list: diff --git a/openlp/plugins/songs/lib/importers/dreambeam.py b/openlp/plugins/songs/lib/importers/dreambeam.py index 532bd865d..71e233eed 100644 --- a/openlp/plugins/songs/lib/importers/dreambeam.py +++ b/openlp/plugins/songs/lib/importers/dreambeam.py @@ -73,7 +73,7 @@ class DreamBeamImport(SongImport): Valid extensions for a DreamBeam song file are: - * \*.xml + * .xml """ def do_import(self): diff --git a/openlp/plugins/songs/lib/importers/easyslides.py b/openlp/plugins/songs/lib/importers/easyslides.py index 8f847c5f3..4a6fc5bf8 100644 --- a/openlp/plugins/songs/lib/importers/easyslides.py +++ b/openlp/plugins/songs/lib/importers/easyslides.py @@ -37,7 +37,7 @@ class EasySlidesImport(SongImport): Import songs exported from EasySlides The format example is here: - http://wiki.openlp.org/Development:EasySlides\_-_Song_Data_Format + http://wiki.openlp.org/Development:EasySlides_-_Song_Data_Format """ def __init__(self, manager, **kwargs): """ @@ -210,7 +210,7 @@ class EasySlidesImport(SongImport): vn = '1' # have we got any digits? # If so, versenumber is everything from the digits to the end - match = re.match('(.*)(\d+.*)', marker) + match = re.match(r'(.*)(\d+.*)', marker) if match: marker = match.group(1).strip() vn = match.group(2) diff --git a/openlp/plugins/songs/lib/importers/foilpresenter.py b/openlp/plugins/songs/lib/importers/foilpresenter.py index 82601f18b..5458b1fad 100644 --- a/openlp/plugins/songs/lib/importers/foilpresenter.py +++ b/openlp/plugins/songs/lib/importers/foilpresenter.py @@ -273,15 +273,15 @@ class FoilPresenter(object): elif copyright.find('C,)') != -1: temp = copyright.partition('C,)') copyright = temp[0] - copyright = re.compile('\\n').sub(' ', copyright) - copyright = re.compile('\(.*\)').sub('', copyright) + copyright = re.compile(r'\\n').sub(' ', copyright) + copyright = re.compile(r'\(.*\)').sub('', copyright) if copyright.find('Rechte') != -1: temp = copyright.partition('Rechte') copyright = temp[0] - markers = ['Text +u\.?n?d? +Melodie[\w\,\. ]*:', - 'Text +u\.?n?d? +Musik', 'T & M', 'Melodie und Satz', - 'Text[\w\,\. ]*:', 'Melodie', 'Musik', 'Satz', - 'Weise', '[dD]eutsch', '[dD]t[\.\:]', 'Englisch', + markers = [r'Text +u\.?n?d? +Melodie[\w\,\. ]*:', + r'Text +u\.?n?d? +Musik', 'T & M', 'Melodie und Satz', + r'Text[\w\,\. ]*:', 'Melodie', 'Musik', 'Satz', + 'Weise', '[dD]eutsch', r'[dD]t[\.\:]', 'Englisch', '[oO]riginal', 'Bearbeitung', '[R|r]efrain'] for marker in markers: copyright = re.compile(marker).sub('', copyright, re.U) @@ -301,17 +301,17 @@ class FoilPresenter(object): break author_temp = [] for author in strings: - temp = re.split(',(?=\D{2})|(?<=\D),|\/(?=\D{3,})|(?<=\D);', author) + temp = re.split(r',(?=\D{2})|(?<=\D),|\/(?=\D{3,})|(?<=\D);', author) for tempx in temp: author_temp.append(tempx) for author in author_temp: - regex = '^[\/,;\-\s\.]+|[\/,;\-\s\.]+$|\s*[0-9]{4}\s*[\-\/]?\s*([0-9]{4})?[\/,;\-\s\.]*$' + regex = r'^[\/,;\-\s\.]+|[\/,;\-\s\.]+$|\s*[0-9]{4}\s*[\-\/]?\s*([0-9]{4})?[\/,;\-\s\.]*$' author = re.compile(regex).sub('', author) - author = re.compile('[0-9]{1,2}\.\s?J(ahr)?h\.|um\s*$|vor\s*$').sub('', author) - author = re.compile('[N|n]ach.*$').sub('', author) + author = re.compile(r'[0-9]{1,2}\.\s?J(ahr)?h\.|um\s*$|vor\s*$').sub('', author) + author = re.compile(r'[N|n]ach.*$').sub('', author) author = author.strip() - if re.search('\w+\.?\s+\w{3,}\s+[a|u]nd\s|\w+\.?\s+\w{3,}\s+&\s', author, re.U): - temp = re.split('\s[a|u]nd\s|\s&\s', author) + if re.search(r'\w+\.?\s+\w{3,}\s+[a|u]nd\s|\w+\.?\s+\w{3,}\s+&\s', author, re.U): + temp = re.split(r'\s[a|u]nd\s|\s&\s', author) for tempx in temp: tempx = tempx.strip() authors.append(tempx) diff --git a/openlp/plugins/songs/lib/importers/lyrix.py b/openlp/plugins/songs/lib/importers/lyrix.py index 386fe43e2..e987b2502 100644 --- a/openlp/plugins/songs/lib/importers/lyrix.py +++ b/openlp/plugins/songs/lib/importers/lyrix.py @@ -80,7 +80,7 @@ class LyrixImport(SongImport): continue # Detect and get CCLI number if line.lower().startswith('ccli'): - ccli = re.findall('\d+', line)[0] + ccli = re.findall(r'\d+', line)[0] try: # If the CCLI was found, we are near the end # Find author diff --git a/openlp/plugins/songs/lib/importers/opensong.py b/openlp/plugins/songs/lib/importers/opensong.py index 25e52777a..470330b3b 100644 --- a/openlp/plugins/songs/lib/importers/opensong.py +++ b/openlp/plugins/songs/lib/importers/opensong.py @@ -156,7 +156,7 @@ class OpenSongImport(SongImport): ustring = str(root.__getattr__(attr)) if isinstance(fn_or_string, str): if attr in ['ccli']: - ustring = ''.join(re.findall('\d+', ustring)) + ustring = ''.join(re.findall(r'\d+', ustring)) if ustring: setattr(self, fn_or_string, int(ustring)) else: @@ -231,7 +231,7 @@ class OpenSongImport(SongImport): content = this_line[1:right_bracket].lower() # have we got any digits? If so, verse number is everything from the digits to the end (openlp does not # have concept of part verses, so just ignore any non integers on the end (including floats)) - match = re.match('(\D*)(\d+)', content) + match = re.match(r'(\D*)(\d+)', content) if match is not None: verse_tag = match.group(1) verse_num = match.group(2) @@ -303,7 +303,7 @@ class OpenSongImport(SongImport): # whitespace. order = order.lower().split() for verse_def in order: - match = re.match('(\D*)(\d+.*)', verse_def) + match = re.match(r'(\D*)(\d+.*)', verse_def) if match is not None: verse_tag = match.group(1) verse_num = match.group(2) diff --git a/openlp/plugins/songs/lib/importers/opspro.py b/openlp/plugins/songs/lib/importers/opspro.py index 5edcfdd39..c21bab2d2 100644 --- a/openlp/plugins/songs/lib/importers/opspro.py +++ b/openlp/plugins/songs/lib/importers/opspro.py @@ -122,7 +122,7 @@ class OPSProImport(SongImport): # Try to split lyrics based on various rules if lyrics: lyrics_text = lyrics.Lyrics - verses = re.split('\r\n\s*?\r\n', lyrics_text) + verses = re.split(r'\r\n\s*?\r\n', lyrics_text) verse_tag_defs = {} verse_tag_texts = {} for verse_text in verses: @@ -130,13 +130,13 @@ class OPSProImport(SongImport): continue verse_def = 'v' # Detect verse number - verse_number = re.match('^(\d+)\r\n', verse_text) + verse_number = re.match(r'^(\d+)\r\n', verse_text) if verse_number: - verse_text = re.sub('^\d+\r\n', '', verse_text) + verse_text = re.sub(r'^\d+\r\n', '', verse_text) verse_def = 'v' + verse_number.group(1) # Detect verse tags - elif re.match('^.+?\:\r\n', verse_text): - tag_match = re.match('^(.+?)\:\r\n(.*)', verse_text, flags=re.DOTALL) + elif re.match(r'^.+?\:\r\n', verse_text): + tag_match = re.match(r'^(.+?)\:\r\n(.*)', verse_text, flags=re.DOTALL) tag = tag_match.group(1).lower() tag = tag.split(' ')[0] verse_text = tag_match.group(2) @@ -147,25 +147,25 @@ class OPSProImport(SongImport): verse_tag_defs[tag] = verse_def verse_tag_texts[tag] = verse_text # Detect tag reference - elif re.match('^\(.*?\)$', verse_text): - tag_match = re.match('^\((.*?)\)$', verse_text) + elif re.match(r'^\(.*?\)$', verse_text): + tag_match = re.match(r'^\((.*?)\)$', verse_text) tag = tag_match.group(1).lower() if tag in verse_tag_defs: verse_text = verse_tag_texts[tag] verse_def = verse_tag_defs[tag] # Detect end tag - elif re.match('^\[slot\]\r\n', verse_text, re.IGNORECASE): + elif re.match(r'^\[slot\]\r\n', verse_text, re.IGNORECASE): verse_def = 'e' - verse_text = re.sub('^\[slot\]\r\n', '', verse_text, flags=re.IGNORECASE) + verse_text = re.sub(r'^\[slot\]\r\n', '', verse_text, flags=re.IGNORECASE) # Replace the join tag with line breaks verse_text = verse_text.replace('[join]', '') # Replace the split tag with line breaks and an optional split - verse_text = re.sub('\[splits?\]', '\r\n[---]', verse_text) + verse_text = re.sub(r'\[splits?\]', '\r\n[---]', verse_text) # Handle translations if lyrics.IsDualLanguage: verse_text = self.handle_translation(verse_text) # Remove comments - verse_text = re.sub('\(.*?\)\r\n', '', verse_text, flags=re.IGNORECASE) + verse_text = re.sub(r'\(.*?\)\r\n', '', verse_text, flags=re.IGNORECASE) self.add_verse(verse_text, verse_def) self.finish() diff --git a/openlp/plugins/songs/lib/importers/powersong.py b/openlp/plugins/songs/lib/importers/powersong.py index d73e3ed9c..c21586166 100644 --- a/openlp/plugins/songs/lib/importers/powersong.py +++ b/openlp/plugins/songs/lib/importers/powersong.py @@ -70,7 +70,7 @@ class PowerSongImport(SongImport): """ Checks if source is a PowerSong 1.0 folder: * is a directory - * contains at least one \*.song file + * contains at least one * .song file :param openlp.core.common.path.Path import_source: Should be a Path object that fulfills the above criteria :return: If the source is valid diff --git a/openlp/plugins/songs/lib/importers/presentationmanager.py b/openlp/plugins/songs/lib/importers/presentationmanager.py index 9b16f24a6..49cd9e220 100644 --- a/openlp/plugins/songs/lib/importers/presentationmanager.py +++ b/openlp/plugins/songs/lib/importers/presentationmanager.py @@ -51,7 +51,7 @@ class PresentationManagerImport(SongImport): encoding = get_file_encoding(file_path)['encoding'] # Open file with detected encoding and remove encoding declaration text = file_path.read_text(encoding=encoding) - text = re.sub('.+\?>\n', '', text) + text = re.sub(r'.+\?>\n', '', text) try: tree = etree.fromstring(text, parser=etree.XMLParser(recover=True)) except ValueError: diff --git a/openlp/plugins/songs/lib/importers/songsoffellowship.py b/openlp/plugins/songs/lib/importers/songsoffellowship.py index d0c0a6720..a5d254252 100644 --- a/openlp/plugins/songs/lib/importers/songsoffellowship.py +++ b/openlp/plugins/songs/lib/importers/songsoffellowship.py @@ -333,7 +333,7 @@ class SongsOfFellowshipImport(OpenOfficeImport): There is a complicated word "One", which is sometimes lower and sometimes upper depending on context. Never mind, keep it lower. """ - text_arr = re.split('(\W+)', text) + text_arr = re.split(r'(\W+)', text) text_arr[0] = text_arr[0].capitalize() for i in range(1, len(text_arr)): # Do not translate these. Fixed strings in SOF song file diff --git a/openlp/plugins/songs/lib/importers/worshipassistant.py b/openlp/plugins/songs/lib/importers/worshipassistant.py index 02df16b4a..6f372876e 100644 --- a/openlp/plugins/songs/lib/importers/worshipassistant.py +++ b/openlp/plugins/songs/lib/importers/worshipassistant.py @@ -142,7 +142,7 @@ class WorshipAssistantImport(SongImport): # drop the square brackets right_bracket = line.find(']') content = line[1:right_bracket].lower() - match = re.match('(\D*)(\d+)', content) + match = re.match(r'(\D*)(\d+)', content) if match is not None: verse_tag = match.group(1) verse_num = match.group(2) diff --git a/scripts/appveyor-webhook.py b/scripts/appveyor-webhook.py index f969b0e70..ce02b7aee 100755 --- a/scripts/appveyor-webhook.py +++ b/scripts/appveyor-webhook.py @@ -81,7 +81,7 @@ def get_version(): latest = output.decode('utf-8').split(':')[0] version_string = latest == revision and tag or 'r%s' % latest # Save decimal version in case we need to do a portable build. - version = latest == revision and tag or '%s.%s' % (tag, latest) + version = latest == revision and tag or '%s-bzr%s' % (tag, latest) return version_string, version @@ -92,21 +92,24 @@ def get_yml(branch, build_type): f = open('appveyor.yml') yml_text = f.read() f.close() - yml_text = yml_text.replace('BRANCHNAME', branch) + version_string, version = get_version() + yml_text = yml_text.replace('TAG', version) if build_type in ['openlp', 'trunk']: + yml_text = yml_text.replace('BRANCHPATH', '~openlp-core/openlp/trunk') yml_text = yml_text.replace('BUILD_DOCS', '$TRUE') else: + yml_text = yml_text.replace('BRANCHPATH', branch.split(':')[1]) yml_text = yml_text.replace('BUILD_DOCS', '$FALSE') - return yml_text + return yml_text, version_string -def hook(webhook_url, yml): +def hook(webhook_url, branch, build_type): """ Activate the webhook to start the build """ + yml, version_string = get_yml(branch, build_type) webhook_element['config'] = yml webhook_element['commit']['message'] = 'Building ' + branch - version_string, version = get_version() webhook_element['commit']['id'] = version_string request = urllib.request.Request(webhook_url) request.add_header('Content-Type', 'application/json;charset=utf-8') @@ -137,7 +140,7 @@ else: if build_type not in ['dev', 'trunk', 'openlp']: print('Invalid build type\nUsage: %s ' % sys.argv[0]) exit() - hook(webhook_url, get_yml(branch, build_type)) + hook(webhook_url, branch, build_type) # Wait 5 seconds to make sure the hook has been triggered time.sleep(5) get_appveyor_build_url(build_type) diff --git a/scripts/appveyor.yml b/scripts/appveyor.yml index 316e5f73b..168b99fb5 100644 --- a/scripts/appveyor.yml +++ b/scripts/appveyor.yml @@ -1,18 +1,17 @@ version: OpenLP-win-ci-b{build} -init: - - choco install -y --force bzr - - set PATH=C:\Program Files (x86)\Bazaar;%PATH% - clone_script: - - bzr checkout --lightweight BRANCHNAME openlp-branch + - curl -L https://bazaar.launchpad.net/BRANCHPATH/tarball -o sourcecode.tar.gz + - 7z e sourcecode.tar.gz + - 7z x sourcecode.tar + - mv BRANCHPATH openlp-branch environment: PYTHON: C:\\Python34 install: # Install dependencies from pypi - - "%PYTHON%\\python.exe -m pip install sqlalchemy alembic chardet beautifulsoup4 Mako nose mock pyodbc==4.0.8 psycopg2 pypiwin32 pyenchant websockets asyncio waitress six webob requests" + - "%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" # 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/) @@ -74,10 +73,10 @@ after_test: 7z x documentation.tar mv ~openlp-core/openlp/documentation documentation cd packaging - &"$env:PYTHON\python.exe" builders/windows-builder.py --skip-update -c windows/config-appveyor.ini -b ../openlp-branch -d ../documentation --portable + &"$env:PYTHON\python.exe" builders/windows-builder.py --skip-update -c windows/config-appveyor.ini -b ../openlp-branch -d ../documentation --portable --tag-override TAG } else { cd packaging - &"$env:PYTHON\python.exe" builders/windows-builder.py --skip-update --skip-translations -c windows/config-appveyor.ini -b ../openlp-branch --portable + &"$env:PYTHON\python.exe" builders/windows-builder.py --skip-update --skip-translations -c windows/config-appveyor.ini -b ../openlp-branch --portable --tag-override TAG } artifacts: diff --git a/scripts/lp-merge.py b/scripts/lp-merge.py index 9bb8ac17a..c10e9e0d2 100755 --- a/scripts/lp-merge.py +++ b/scripts/lp-merge.py @@ -124,7 +124,7 @@ def get_merge_info(url): script_tag = soup.find('script', attrs={"id": "codereview-script"}) content = script_tag.contents[0] start_pos = content.find('source_revid') + 16 - pattern = re.compile('.*\w-\d\d\d\d\d+') + pattern = re.compile(r'.*\w-\d\d\d\d\d+') match = pattern.match(content[start_pos:]) merge_info['author_email'] = match.group()[:-15] # Launchpad doesn't supply the author's true name, so we'll just grab whatever they use for display on LP diff --git a/setup.cfg b/setup.cfg index e2ef020e0..223e5c7c2 100644 --- a/setup.cfg +++ b/setup.cfg @@ -15,5 +15,10 @@ ignore = E402 [pycodestyle] exclude = resources.py,vlc.py max-line-length = 120 -ignore = E402 +# Ignoring: +# E402... +# E722 do not use bare 'except' +# W503 line break before binary operator +# W504 line break after binary operator +ignore = E402,E722,W503,W504 diff --git a/tests/functional/openlp_core/api/test_tab.py b/tests/functional/openlp_core/api/test_tab.py index da1c79f3c..1f61673f4 100644 --- a/tests/functional/openlp_core/api/test_tab.py +++ b/tests/functional/openlp_core/api/test_tab.py @@ -85,7 +85,7 @@ class TestApiTab(TestCase, TestMixin): ip_address = self.form.get_ip_address(ZERO_URL) # THEN: the default ip address will be returned - assert re.match('\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}', ip_address), \ + assert re.match(r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}', ip_address), \ 'The return value should be a valid ip address' assert ip_address in ip4_list, 'The return address should be in the list of local IP addresses' diff --git a/tests/functional/openlp_core/common/test_httputils.py b/tests/functional/openlp_core/common/test_httputils.py index 7d3966279..0552cce8f 100644 --- a/tests/functional/openlp_core/common/test_httputils.py +++ b/tests/functional/openlp_core/common/test_httputils.py @@ -27,13 +27,14 @@ import tempfile from unittest import TestCase from unittest.mock import MagicMock, patch -from openlp.core.common.httputils import get_user_agent, get_web_page, get_url_file_size, download_file +from openlp.core.common.httputils import ProxyMode, download_file, get_proxy_settings, get_url_file_size, \ + get_user_agent, get_web_page from openlp.core.common.path import Path +from openlp.core.common.settings import Settings from tests.helpers.testmixin import TestMixin class TestHttpUtils(TestCase, TestMixin): - """ A test suite to test out various http helper functions. """ @@ -240,3 +241,119 @@ class TestHttpUtils(TestCase, TestMixin): # THEN: socket.timeout should have been caught # NOTE: Test is if $tmpdir/tempfile is still there, then test fails since ftw deletes bad downloaded files assert os.path.exists(self.tempfile) is False, 'tempfile should have been deleted' + + +class TestGetProxySettings(TestCase, TestMixin): + def setUp(self): + self.build_settings() + self.addCleanup(self.destroy_settings) + + @patch('openlp.core.common.httputils.Settings') + def test_mode_arg_specified(self, MockSettings): + """ + Test that the argument is used rather than reading the 'advanced/proxy mode' setting + """ + # GIVEN: Mocked settings + mocked_settings = MagicMock() + MockSettings.return_value = mocked_settings + + # WHEN: Calling `get_proxy_settings` with the mode arg specified + get_proxy_settings(mode=ProxyMode.NO_PROXY) + + # THEN: The mode arg should have been used rather than looking it up in the settings + mocked_settings.value.assert_not_called() + + @patch('openlp.core.common.httputils.Settings') + def test_mode_incorrect_arg_specified(self, MockSettings): + """ + Test that the system settings are used when the mode arg specieied is invalid + """ + # GIVEN: Mocked settings + mocked_settings = MagicMock() + MockSettings.return_value = mocked_settings + + # WHEN: Calling `get_proxy_settings` with an invalid mode arg specified + result = get_proxy_settings(mode='qwerty') + + # THEN: An None should be returned + mocked_settings.value.assert_not_called() + assert result is None + + def test_no_proxy_mode(self): + """ + Test that a dictionary with http and https values are set to None is returned, when `NO_PROXY` mode is specified + """ + # GIVEN: A `proxy mode` setting of NO_PROXY + Settings().setValue('advanced/proxy mode', ProxyMode.NO_PROXY) + + # WHEN: Calling `get_proxy_settings` + result = get_proxy_settings() + + # THEN: The returned value should be a dictionary with http and https values set to None + assert result == {'http': None, 'https': None} + + def test_system_proxy_mode(self): + """ + Test that None is returned, when `SYSTEM_PROXY` mode is specified + """ + # GIVEN: A `proxy mode` setting of SYSTEM_PROXY + Settings().setValue('advanced/proxy mode', ProxyMode.SYSTEM_PROXY) + + # WHEN: Calling `get_proxy_settings` + result = get_proxy_settings() + + # THEN: The returned value should be None + assert result is None + + def test_manual_proxy_mode_no_auth(self): + """ + Test that the correct proxy addresses are returned when basic authentication is not used + """ + # GIVEN: A `proxy mode` setting of MANUAL_PROXY with proxy servers, but no auth credentials are supplied + Settings().setValue('advanced/proxy mode', ProxyMode.MANUAL_PROXY) + Settings().setValue('advanced/proxy http', 'testhttp.server:port') + Settings().setValue('advanced/proxy https', 'testhttps.server:port') + Settings().setValue('advanced/proxy username', '') + Settings().setValue('advanced/proxy password', '') + + # WHEN: Calling `get_proxy_settings` + result = get_proxy_settings() + + # THEN: The returned value should be the proxy servers without authentication + assert result == {'http': 'http://testhttp.server:port', 'https': 'https://testhttps.server:port'} + + def test_manual_proxy_mode_auth(self): + """ + Test that the correct proxy addresses are returned when basic authentication is used + """ + # GIVEN: A `proxy mode` setting of MANUAL_PROXY with proxy servers and auth credentials supplied + Settings().setValue('advanced/proxy mode', ProxyMode.MANUAL_PROXY) + Settings().setValue('advanced/proxy http', 'testhttp.server:port') + Settings().setValue('advanced/proxy https', 'testhttps.server:port') + Settings().setValue('advanced/proxy username', 'user') + Settings().setValue('advanced/proxy password', 'pass') + + # WHEN: Calling `get_proxy_settings` + result = get_proxy_settings() + + # THEN: The returned value should be the proxy servers with the authentication credentials + assert result == {'http': 'http://user:pass@testhttp.server:port', + 'https': 'https://user:pass@testhttps.server:port'} + + def test_manual_proxy_mode_no_servers(self): + """ + Test that the system proxies are overidden when the MANUAL_PROXY mode is specified, but no server addresses are + supplied + """ + # GIVEN: A `proxy mode` setting of MANUAL_PROXY with no servers specified + Settings().setValue('advanced/proxy mode', ProxyMode.MANUAL_PROXY) + Settings().setValue('advanced/proxy http', '') + Settings().setValue('advanced/proxy https', '') + Settings().setValue('advanced/proxy username', 'user') + Settings().setValue('advanced/proxy password', 'pass') + + # WHEN: Calling `get_proxy_settings` + result = get_proxy_settings() + + # THEN: The returned value should be the proxy servers set to None + assert result == {'http': None, 'https': None} diff --git a/tests/functional/openlp_core/common/test_init.py b/tests/functional/openlp_core/common/test_init.py index 83f0c1c7c..f30215397 100644 --- a/tests/functional/openlp_core/common/test_init.py +++ b/tests/functional/openlp_core/common/test_init.py @@ -238,8 +238,8 @@ class TestInit(TestCase, TestMixin): Test the clean_filename() function """ # GIVEN: A invalid file name and the valid file name. - invalid_name = 'A_file_with_invalid_characters_[\\/:\*\?"<>\|\+\[\]%].py' - wanted_name = 'A_file_with_invalid_characters______________________.py' + invalid_name = 'A_file_with_invalid_characters_[\\/:*?"<>|+[]%].py' + wanted_name = 'A_file_with_invalid_characters________________.py' # WHEN: Clean the name. result = clean_filename(invalid_name) diff --git a/tests/functional/openlp_core/common/test_network_interfaces.py b/tests/functional/openlp_core/common/test_network_interfaces.py new file mode 100644 index 000000000..d1547bd0a --- /dev/null +++ b/tests/functional/openlp_core/common/test_network_interfaces.py @@ -0,0 +1,78 @@ +# -*- 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/functional/openlp_core/lib/test_htmlbuilder.py b/tests/functional/openlp_core/lib/test_htmlbuilder.py index 11bfde2b6..02a390783 100644 --- a/tests/functional/openlp_core/lib/test_htmlbuilder.py +++ b/tests/functional/openlp_core/lib/test_htmlbuilder.py @@ -12,7 +12,7 @@ from openlp.core.lib.htmlbuilder import build_html, build_background_css, build_ from openlp.core.lib.theme import HorizontalType, VerticalType from tests.helpers.testmixin import TestMixin -HTML = """ +HTML = r""" @@ -121,7 +121,7 @@ HTML = """ } function show_text(new_text){ - var match = /-webkit-text-fill-color:[^;"]+/gi; + var match = /-webkit-text-fill-color:[^;\"]+/gi; if(timer != null) clearTimeout(timer); /* diff --git a/tests/interfaces/openlp_core/ui/test_filerenamedialog.py b/tests/interfaces/openlp_core/ui/test_filerenamedialog.py index e268aef2f..ebff60310 100644 --- a/tests/interfaces/openlp_core/ui/test_filerenamedialog.py +++ b/tests/interfaces/openlp_core/ui/test_filerenamedialog.py @@ -99,7 +99,7 @@ class TestStartFileRenameForm(TestCase, TestMixin): # GIVEN: QLineEdit with a validator set with illegal file name characters. # WHEN: 'Typing' a string containing invalid file characters. - QtTest.QTest.keyClicks(self.form.file_name_edit, 'I/n\\v?a*l|i \F[i\l]e" :N+a%me') + QtTest.QTest.keyClicks(self.form.file_name_edit, r'I/n\\v?a*l|i \F[i\l]e" :N+a%me') # THEN: The text in the QLineEdit should be the same as the input string with the invalid characters filtered # out. diff --git a/tests/interfaces/openlp_core/widgets/test_widgets.py b/tests/interfaces/openlp_core/widgets/test_widgets.py new file mode 100644 index 000000000..5353596fd --- /dev/null +++ b/tests/interfaces/openlp_core/widgets/test_widgets.py @@ -0,0 +1,174 @@ +# -*- 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 # +############################################################################### +""" +Module to test the custom widgets. +""" +from unittest import TestCase +from unittest.mock import MagicMock, call, patch + +from openlp.core.common.registry import Registry +from openlp.core.common.settings import ProxyMode +from openlp.core.widgets.widgets import ProxyWidget +from tests.helpers.testmixin import TestMixin + + +class TestProxyWidget(TestCase, TestMixin): + """ + Test the EditCustomForm. + """ + def setUp(self): + """ + Create the UI + """ + Registry.create() + self.setup_application() + + def test_radio_button_exclusivity_no_proxy(self): + """ + Test that only one radio button can be checked at a time, and that the line edits are only enabled when the + `manual_proxy_radio` is checked + """ + # GIVEN: An instance of the `openlp.core.common.widgets.widgets.ProxyWidget` with a radio already checked + proxy_widget = ProxyWidget() + proxy_widget.manual_proxy_radio.setChecked(True) + + # WHEN: 'Checking' the `no_proxy_radio` button + proxy_widget.no_proxy_radio.setChecked(True) + + # THEN: The other radio buttons should not be checked and the line edits should not be enabled + assert proxy_widget.use_sysem_proxy_radio.isChecked() is False + assert proxy_widget.manual_proxy_radio.isChecked() is False + assert proxy_widget.http_edit.isEnabled() is False + assert proxy_widget.https_edit.isEnabled() is False + assert proxy_widget.username_edit.isEnabled() is False + assert proxy_widget.password_edit.isEnabled() is False + + def test_radio_button_exclusivity_system_proxy(self): + """ + Test that only one radio button can be checked at a time, and that the line edits are only enabled when the + `manual_proxy_radio` is checked + """ + # GIVEN: An instance of the `openlp.core.common.widgets.widgets.ProxyWidget` with a radio already checked + proxy_widget = ProxyWidget() + proxy_widget.manual_proxy_radio.setChecked(True) + + # WHEN: 'Checking' the `use_sysem_proxy_radio` button + proxy_widget.use_sysem_proxy_radio.setChecked(True) + + # THEN: The other radio buttons should not be checked and the line edits should not be enabled + assert proxy_widget.no_proxy_radio.isChecked() is False + assert proxy_widget.manual_proxy_radio.isChecked() is False + assert proxy_widget.http_edit.isEnabled() is False + assert proxy_widget.https_edit.isEnabled() is False + assert proxy_widget.username_edit.isEnabled() is False + assert proxy_widget.password_edit.isEnabled() is False + + def test_radio_button_exclusivity_manual_proxy(self): + """ + Test that only one radio button can be checked at a time, and that the line edits are only enabled when the + `manual_proxy_radio` is checked + """ + # GIVEN: An instance of the `openlp.core.common.widgets.widgets.ProxyWidget` with a radio already checked + proxy_widget = ProxyWidget() + proxy_widget.no_proxy_radio.setChecked(True) + + # WHEN: 'Checking' the `manual_proxy_radio` button + proxy_widget.manual_proxy_radio.setChecked(True) + + # THEN: The other radio buttons should not be checked and the line edits should be enabled + assert proxy_widget.no_proxy_radio.isChecked() is False + assert proxy_widget.use_sysem_proxy_radio.isChecked() is False + assert proxy_widget.http_edit.isEnabled() is True + assert proxy_widget.https_edit.isEnabled() is True + assert proxy_widget.username_edit.isEnabled() is True + assert proxy_widget.password_edit.isEnabled() is True + + def test_proxy_widget_load_default_settings(self): + """ + Test that the default settings are loaded from the config correctly + """ + # GIVEN: And instance of the widget with default settings + proxy_widget = ProxyWidget() + + # WHEN: Calling the `load` method + proxy_widget.load() + + # THEN: The widget should be in its default state + assert proxy_widget.use_sysem_proxy_radio.isChecked() is True + assert proxy_widget.http_edit.text() == '' + assert proxy_widget.https_edit.text() == '' + assert proxy_widget.username_edit.text() == '' + assert proxy_widget.password_edit.text() == '' + + @patch.object(ProxyWidget, 'load') + @patch('openlp.core.widgets.widgets.Settings') + def test_proxy_widget_save_no_proxy_settings(self, settings_patcher, proxy_widget_load_patcher): + """ + Test that the settings are saved correctly + """ + # GIVEN: A Mocked settings instance of the proxy widget with some known values set + settings_instance = MagicMock() + settings_patcher.return_value = settings_instance + proxy_widget = ProxyWidget() + proxy_widget.no_proxy_radio.setChecked(True) + proxy_widget.http_edit.setText('') + proxy_widget.https_edit.setText('') + proxy_widget.username_edit.setText('') + proxy_widget.password_edit.setText('') + + # WHEN: Calling save + proxy_widget.save() + + # THEN: The settings should be set as expected + settings_instance.setValue.assert_has_calls( + [call('advanced/proxy mode', ProxyMode.NO_PROXY), + call('advanced/proxy http', ''), + call('advanced/proxy https', ''), + call('advanced/proxy username', ''), + call('advanced/proxy password', '')]) + + @patch.object(ProxyWidget, 'load') + @patch('openlp.core.widgets.widgets.Settings') + def test_proxy_widget_save_manual_settings(self, settings_patcher, proxy_widget_load_patcher): + """ + Test that the settings are saved correctly + """ + # GIVEN: A Mocked and instance of the proxy widget with some known values set + settings_instance = MagicMock() + settings_patcher.return_value = settings_instance + proxy_widget = ProxyWidget() + proxy_widget.manual_proxy_radio.setChecked(True) + proxy_widget.http_edit.setText('http_proxy_server:port') + proxy_widget.https_edit.setText('https_proxy_server:port') + proxy_widget.username_edit.setText('username') + proxy_widget.password_edit.setText('password') + + # WHEN: Calling save + proxy_widget.save() + + # THEN: The settings should be set as expected + settings_instance.setValue.assert_has_calls( + [call('advanced/proxy mode', ProxyMode.MANUAL_PROXY), + call('advanced/proxy http', 'http_proxy_server:port'), + call('advanced/proxy https', 'https_proxy_server:port'), + call('advanced/proxy username', 'username'), + call('advanced/proxy password', 'password')]) diff --git a/tests/utils/test_pylint.py b/tests/utils/test_pylint.py index 8ef9425c9..da83fc1e7 100644 --- a/tests/utils/test_pylint.py +++ b/tests/utils/test_pylint.py @@ -68,7 +68,7 @@ class TestPylint(TestCase): # WHEN: Running pylint (pylint_stdout, pylint_stderr) = \ - lint.py_run('openlp --errors-only --disable={disabled} --enable={enabled} ' + lint.py_run('openlp --errors-only -j 4 --disable={disabled} --enable={enabled} ' '--reports=no --output-format=parseable'.format(disabled=disabled_checks, enabled=enabled_checks), **pylint_kwargs) @@ -88,7 +88,7 @@ class TestPylint(TestCase): filtered_output = '' for line in pylint_output.splitlines(): # Filter out module info lines - if line.startswith('**'): + if '***' in line: continue # Filter out undefined-variable error releated to WindowsError elif 'undefined-variable' in line and 'WindowsError' in line: