This commit is contained in:
Philip Ridout 2018-10-24 22:02:06 +01:00
commit a867c54b94
37 changed files with 1212 additions and 321 deletions

View File

@ -290,7 +290,7 @@ def parse_options(args=None):
:return: a tuple of parsed options of type optparse.Value and a list of remaining argsZ :return: a tuple of parsed options of type optparse.Value and a list of remaining argsZ
""" """
# Set up command line options. # 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', parser.add_argument('-e', '--no-error-form', dest='no_error_form', action='store_true',
help='Disable the error notification form.') help='Disable the error notification form.')
parser.add_argument('-l', '--log-level', dest='loglevel', default='warning', metavar='LEVEL', parser.add_argument('-l', '--log-level', dest='loglevel', default='warning', metavar='LEVEL',

View File

@ -60,7 +60,6 @@ def get_local_ip4():
:returns: Dict of interfaces :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') log.debug('Getting local IPv4 interface(es) information')
my_ip4 = {} my_ip4 = {}
for iface in QNetworkInterface.allInterfaces(): for iface in QNetworkInterface.allInterfaces():
@ -70,8 +69,6 @@ def get_local_ip4():
log.debug('Checking address(es) protocol') log.debug('Checking address(es) protocol')
for address in iface.addressEntries(): for address in iface.addressEntries():
ip = address.ip() 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') log.debug('Checking for protocol == IPv4Protocol')
if ip.protocol() == QAbstractSocket.IPv4Protocol: if ip.protocol() == QAbstractSocket.IPv4Protocol:
log.debug('Getting interface information') log.debug('Getting interface information')
@ -83,12 +80,13 @@ def get_local_ip4():
ip.toIPv4Address()).toString() ip.toIPv4Address()).toString()
} }
log.debug('Adding {iface} to active list'.format(iface=iface.name())) 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: if 'localhost' in my_ip4:
log.debug('Renaming windows localhost to lo') log.debug('Renaming windows localhost to lo')
my_ip4['lo'] = my_ip4['localhost'] my_ip4['lo'] = my_ip4['localhost']
my_ip4.pop('localhost') my_ip4.pop('localhost')
if len(my_ip4) == 0:
log.warning('No active IPv4 network interfaces detected')
if len(my_ip4) == 1: if len(my_ip4) == 1:
if 'lo' in my_ip4: if 'lo' in my_ip4:
# No active interfaces - so leave localhost in there # No active interfaces - so leave localhost in there

View File

@ -26,19 +26,14 @@ import logging
import os import os
import sys import sys
import appdirs
import openlp import openlp
from openlp.core.common import get_frozen_path, is_macosx, is_win from openlp.core.common import get_frozen_path, is_macosx, is_win
from openlp.core.common.path import Path, create_paths from openlp.core.common.path import Path, create_paths
from openlp.core.common.settings import Settings 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__) log = logging.getLogger(__name__)
FROZEN_APP_PATH = Path(sys.argv[0]).parent FROZEN_APP_PATH = Path(sys.argv[0]).parent
@ -144,8 +139,10 @@ def _get_os_dir_path(dir_type):
elif dir_type == AppLocation.LanguageDir: elif dir_type == AppLocation.LanguageDir:
return Path(openlp.__file__).parent return Path(openlp.__file__).parent
return openlp_folder_path 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: if dir_type == AppLocation.DataDir:
return openlp_folder_path / 'Data' return openlp_folder_path / 'Data'
elif dir_type == AppLocation.LanguageDir: elif dir_type == AppLocation.LanguageDir:
@ -153,15 +150,15 @@ def _get_os_dir_path(dir_type):
return openlp_folder_path return openlp_folder_path
else: else:
if dir_type == AppLocation.LanguageDir: 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(): if directory.exists():
return directory return directory
return Path('/usr', 'share', 'openlp') return Path(site_dirs[1])
if XDG_BASE_AVAILABLE: if dir_type == AppLocation.DataDir:
if dir_type == AppLocation.DataDir: return Path(dirs.user_data_dir)
return Path(BaseDirectory.xdg_data_home, 'openlp') elif dir_type == AppLocation.CacheDir:
elif dir_type == AppLocation.CacheDir: return Path(dirs.user_cache_dir)
return Path(BaseDirectory.xdg_cache_home, 'openlp')
if dir_type == AppLocation.DataDir: if dir_type == AppLocation.DataDir:
return Path(os.getenv('HOME'), '.openlp', 'data') return Path(os.getenv('HOME'), '.openlp', 'data')
return Path(os.getenv('HOME'), '.openlp') return Path(os.getenv('HOME'), '.openlp')

View File

@ -58,8 +58,7 @@ class Registry(object):
registry.functions_list = {} registry.functions_list = {}
registry.working_flags = {} registry.working_flags = {}
# Allow the tests to remove Registry entries but not the live system # 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 = 'nose' in sys.argv[0] or 'pytest' in sys.argv[0]
registry.running_under_test = 'pytest' in sys.argv[0]
registry.initialising = True registry.initialising = True
return registry return registry

View File

@ -1,4 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 # vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
############################################################################### ###############################################################################
@ -247,7 +247,8 @@ class Settings(QtCore.QSettings):
'projector/last directory export': None, 'projector/last directory export': None,
'projector/poll time': 20, # PJLink timeout is 30 seconds 'projector/poll time': 20, # PJLink timeout is 30 seconds
'projector/socket timeout': 5, # 5 second socket timeout '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__ = '' __file_path__ = ''
# Settings upgrades prior to 3.0 # Settings upgrades prior to 3.0
@ -306,7 +307,11 @@ class Settings(QtCore.QSettings):
('songuasge/db database', 'songusage/db database', []), ('songuasge/db database', 'songusage/db database', []),
('presentations / Powerpoint Viewer', '', []), ('presentations / Powerpoint Viewer', '', []),
(['core/monitor', 'core/x position', 'core/y position', 'core/height', 'core/width', 'core/override', (['core/monitor', 'core/x position', 'core/y position', 'core/height', 'core/width', 'core/override',
'core/display on monitor'], 'core/screens', [(upgrade_screens, [1, 0, 0, None, None, False, False])]) 'core/display on monitor'], 'core/screens', [(upgrade_screens, [1, 0, 0, None, None, False, False])]),
('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 @staticmethod

View File

@ -399,4 +399,4 @@ def create_separated_list(string_list):
last=string_list[-1]) last=string_list[-1])
else: else:
list_to_string = '' list_to_string = ''
return list_to_string return list_to_string

View File

@ -180,6 +180,7 @@ class ProjectorEditForm(QtWidgets.QDialog, Ui_ProjectorEditForm):
Validate input before accepting input. Validate input before accepting input.
""" """
log.debug('accept_me() signal received') log.debug('accept_me() signal received')
valid = True
if len(self.name_text.text().strip()) < 1: if len(self.name_text.text().strip()) < 1:
QtWidgets.QMessageBox.warning(self, QtWidgets.QMessageBox.warning(self,
translate('OpenLP.ProjectorEdit', 'Name Not Set'), translate('OpenLP.ProjectorEdit', 'Name Not Set'),

View File

@ -31,13 +31,14 @@ from PyQt5 import QtCore, QtGui, QtWidgets
from openlp.core.common.i18n import translate from openlp.core.common.i18n import translate
from openlp.core.common.mixins import LogMixin, RegistryProperties 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.common.settings import Settings
from openlp.core.lib.ui import create_widget_action from openlp.core.lib.ui import create_widget_action
from openlp.core.projectors import DialogSourceStyle from openlp.core.projectors import DialogSourceStyle
from openlp.core.projectors.constants import E_AUTHENTICATION, E_ERROR, E_NETWORK, E_NOT_CONNECTED, E_SOCKET_TIMEOUT, \ from openlp.core.projectors.constants import E_AUTHENTICATION, E_ERROR, E_NETWORK, E_NOT_CONNECTED, E_SOCKET_TIMEOUT,\
E_UNKNOWN_SOCKET_ERROR, PJLINK_PORT, QSOCKET_STATE, S_CONNECTED, S_CONNECTING, S_COOLDOWN, S_INITIALIZE, \ E_UNKNOWN_SOCKET_ERROR, QSOCKET_STATE, S_CONNECTED, S_CONNECTING, S_COOLDOWN, S_INITIALIZE, S_NOT_CONNECTED, S_OFF,\
S_NOT_CONNECTED, S_OFF, S_ON, S_STANDBY, S_WARMUP, STATUS_CODE, STATUS_MSG S_ON, S_STANDBY, S_WARMUP, STATUS_CODE, STATUS_MSG
from openlp.core.projectors.db import ProjectorDB from openlp.core.projectors.db import ProjectorDB
from openlp.core.projectors.editform import ProjectorEditForm from openlp.core.projectors.editform import ProjectorEditForm
from openlp.core.projectors.pjlink import PJLink, PJLinkUDP from openlp.core.projectors.pjlink import PJLink, PJLinkUDP
@ -297,7 +298,7 @@ class ProjectorManager(QtWidgets.QWidget, RegistryBase, UiProjectorManager, LogM
self.projector_list = [] self.projector_list = []
self.source_select_form = None self.source_select_form = None
# Dictionary of PJLinkUDP objects to listen for UDP broadcasts from PJLink 2+ projectors. # 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 = {} self.pjlink_udp = {}
# Dict for matching projector status to display icon # Dict for matching projector status to display icon
self.status_icons = { self.status_icons = {
@ -335,10 +336,6 @@ class ProjectorManager(QtWidgets.QWidget, RegistryBase, UiProjectorManager, LogM
""" """
Post-initialize setups. 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 # Set 1.5 second delay before loading all projectors
if self.autostart: if self.autostart:
log.debug('Delaying 1.5 seconds before loading all projectors') log.debug('Delaying 1.5 seconds before loading all projectors')
@ -351,6 +348,36 @@ class ProjectorManager(QtWidgets.QWidget, RegistryBase, UiProjectorManager, LogM
self.projector_form.editProjector.connect(self.edit_projector_from_wizard) self.projector_form.editProjector.connect(self.edit_projector_from_wizard)
self.projector_list_widget.itemSelectionChanged.connect(self.update_icons) 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): def get_settings(self):
""" """
Retrieve the saved settings Retrieve the saved settings
@ -518,25 +545,22 @@ class ProjectorManager(QtWidgets.QWidget, RegistryBase, UiProjectorManager, LogM
except (AttributeError, TypeError): except (AttributeError, TypeError):
pass pass
try: try:
projector.poll_timer.stop() projector.link.poll_timer.stop()
projector.poll_timer.timeout.disconnect(projector.link.poll_loop) projector.link.poll_timer.timeout.disconnect(projector.link.poll_loop)
except (AttributeError, TypeError): except (AttributeError, TypeError):
pass pass
try: try:
projector.socket_timer.stop() projector.link.socket_timer.stop()
projector.socket_timer.timeout.disconnect(projector.link.socket_abort) projector.link.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)
except (AttributeError, TypeError): except (AttributeError, TypeError):
pass pass
old_port = projector.link.port
# Rebuild projector list # Rebuild projector list
new_list = [] new_list = []
for item in self.projector_list: for item in self.projector_list:
if item.link.db_item.id == projector.link.db_item.id: if item.link.db_item.id == projector.link.db_item.id:
log.debug('Removing projector "{item}"'.format(item=item.link.name))
continue continue
new_list.append(item) new_list.append(item)
self.projector_list = new_list self.projector_list = new_list
@ -546,6 +570,7 @@ class ProjectorManager(QtWidgets.QWidget, RegistryBase, UiProjectorManager, LogM
log.warning('Delete projector {item} failed'.format(item=projector.db_item)) log.warning('Delete projector {item} failed'.format(item=projector.db_item))
for item in self.projector_list: for item in self.projector_list:
log.debug('New projector list - item: {ip} {name}'.format(ip=item.link.ip, name=item.link.name)) 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): def on_disconnect_projector(self, opt=None):
""" """
@ -748,15 +773,8 @@ class ProjectorManager(QtWidgets.QWidget, RegistryBase, UiProjectorManager, LogM
item.link.projectorAuthentication.connect(self.authentication_error) item.link.projectorAuthentication.connect(self.authentication_error)
item.link.projectorNoAuthentication.connect(self.no_authentication_error) item.link.projectorNoAuthentication.connect(self.no_authentication_error)
item.link.projectorUpdateIcons.connect(self.update_icons) item.link.projectorUpdateIcons.connect(self.update_icons)
# Connect UDP signal to projector instances with same port # Add UDP listener for new projector port
if item.link.port not in self.pjlink_udp: self.udp_listen_add(item.link.port)
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)
self.projector_list.append(item) self.projector_list.append(item)
if start: if start:
item.link.connect_to_host() item.link.connect_to_host()
@ -783,13 +801,25 @@ class ProjectorManager(QtWidgets.QWidget, RegistryBase, UiProjectorManager, LogM
:param projector: Projector() instance of projector with updated information :param projector: Projector() instance of projector with updated information
""" """
log.debug('edit_projector_from_wizard(ip={ip})'.format(ip=projector.ip)) 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.name = projector.name
self.old_projector.link.ip = projector.ip self.old_projector.link.ip = projector.ip
self.old_projector.link.pin = None if projector.pin == '' else projector.pin 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.location = projector.location
self.old_projector.link.notes = projector.notes self.old_projector.link.notes = projector.notes
self.old_projector.widget.setText(projector.name) 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): def _load_projectors(self):
"""' """'

View File

@ -99,32 +99,51 @@ class PJLinkUDP(QtNetwork.QUdpSocket):
self.search_active = False self.search_active = False
self.search_time = 30000 # 30 seconds for allowed time self.search_time = 30000 # 30 seconds for allowed time
self.search_timer = QtCore.QTimer() 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) 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() @QtCore.pyqtSlot()
def get_datagram(self): def get_datagram(self):
""" """
Retrieve packet and basic checks 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() read_size = self.pendingDatagramSize()
if -1 == read_size: if -1 == read_size:
log.warning('(UDP) No data (-1)') log.warning('(UDP:{port}) No data (-1)'.format(port=self.port))
return return
elif 0 == read_size: 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 return
elif read_size > PJLINK_MAX_PACKET: 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 return
data_in, peer_host, peer_port = self.readDatagram(read_size) data_in, peer_host, peer_port = self.readDatagram(read_size)
data = data_in.decode('utf-8') if isinstance(data_in, bytes) else data_in 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), log.debug('(UDP:{port}) {size} bytes received from {adx}'.format(size=len(data),
adx=peer_host.toString(), adx=peer_host.toString(),
port=self.port)) port=self.port))
log.debug('(UDP) packet "{data}"'.format(data=data)) log.debug('(UDP:{port}) packet "{data}"'.format(data=data, port=self.port))
log.debug('(UDP) Sending data_received signal to projectors') log.debug('(UDP:{port}) Sending data_received signal to projectors'.format(port=self.port))
self.data_received.emit(peer_host, self.localPort(), data) self.data_received.emit(peer_host, self.localPort(), data)
return return
@ -144,6 +163,25 @@ class PJLinkUDP(QtNetwork.QUdpSocket):
self.search_active = False self.search_active = False
self.search_timer.stop() 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): class PJLinkCommands(object):
""" """

View File

@ -27,6 +27,7 @@ import logging
from PyQt5 import QtWidgets from PyQt5 import QtWidgets
from openlp.core.common.i18n import UiStrings, translate 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.common.settings import Settings
from openlp.core.lib.settingstab import SettingsTab from openlp.core.lib.settingstab import SettingsTab
from openlp.core.projectors import DialogSourceStyle from openlp.core.projectors import DialogSourceStyle
@ -48,8 +49,11 @@ class ProjectorTab(SettingsTab):
:param parent: Parent widget :param parent: Parent widget
""" """
self.icon_path = UiIcons().projector self.icon_path = UiIcons().projector
self.udp_listeners = {} # Key on port number
projector_translated = translate('OpenLP.ProjectorTab', 'Projector') projector_translated = translate('OpenLP.ProjectorTab', 'Projector')
super(ProjectorTab, self).__init__(parent, 'Projector', projector_translated) 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 setup_ui(self): def setup_ui(self):
""" """
@ -91,6 +95,10 @@ class ProjectorTab(SettingsTab):
self.connect_box_layout.addRow(self.dialog_type_label, self.dialog_type_combo_box) self.connect_box_layout.addRow(self.dialog_type_label, self.dialog_type_combo_box)
self.left_layout.addStretch() self.left_layout.addStretch()
self.dialog_type_combo_box.activated.connect(self.on_dialog_type_combo_box_changed) 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) # Connect on LKUP packet received (PJLink v2+ only)
self.connect_on_linkup = QtWidgets.QCheckBox(self.connect_box) self.connect_on_linkup = QtWidgets.QCheckBox(self.connect_box)
self.connect_on_linkup.setObjectName('connect_on_linkup') self.connect_on_linkup.setObjectName('connect_on_linkup')
@ -117,6 +125,9 @@ class ProjectorTab(SettingsTab):
translate('OpenLP.ProjectorTab', 'Single dialog box')) translate('OpenLP.ProjectorTab', 'Single dialog box'))
self.connect_on_linkup.setText( self.connect_on_linkup.setText(
translate('OpenLP.ProjectorTab', 'Connect to projector when LINKUP received (v2 only)')) 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): def load(self):
""" """
@ -126,6 +137,7 @@ class ProjectorTab(SettingsTab):
self.socket_timeout_spin_box.setValue(Settings().value('projector/socket timeout')) self.socket_timeout_spin_box.setValue(Settings().value('projector/socket timeout'))
self.socket_poll_spin_box.setValue(Settings().value('projector/poll time')) self.socket_poll_spin_box.setValue(Settings().value('projector/poll time'))
self.dialog_type_combo_box.setCurrentIndex(Settings().value('projector/source dialog type')) 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')) self.connect_on_linkup.setChecked(Settings().value('projector/connect when LKUP received'))
def save(self): def save(self):
@ -137,6 +149,41 @@ class ProjectorTab(SettingsTab):
Settings().setValue('projector/poll time', self.socket_poll_spin_box.value()) 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/source dialog type', self.dialog_type_combo_box.currentIndex())
Settings().setValue('projector/connect when LKUP received', self.connect_on_linkup.isChecked()) 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): def on_dialog_type_combo_box_changed(self):
self.dialog_type = self.dialog_type_combo_box.currentIndex() 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())

View File

@ -112,8 +112,8 @@ class UiAboutDialog(object):
'Andreas "googol" Preikschat', 'Ken "alisonken1" Roberts', 'Raoul "superfly" Snyman', 'Andreas "googol" Preikschat', 'Ken "alisonken1" Roberts', 'Raoul "superfly" Snyman',
'Jonathan "springermac" Springer', 'Philip "Phill" Ridout'] 'Jonathan "springermac" Springer', 'Philip "Phill" Ridout']
contributors = ['Stuart "sibecker" Becker', 'Gerald "jerryb" Britton', 'Jonathan "gushie" Corwin', contributors = ['Stuart "sibecker" Becker', 'Gerald "jerryb" Britton', 'Jonathan "gushie" Corwin',
'Samuel "MrGamgee" Findlay', 'Michael "cocooncrash" Gorven', 'Scott "sguerrieri" Guerrieri', 'Samuel "MrGamgee" Findlay', 'Bastian Germann', 'Michael "cocooncrash" Gorven',
'Simon Hanna', 'Chris Hill', 'Scott "sguerrieri" Guerrieri', 'Simon Hanna', 'Chris Hill',
'Matthias "matthub" Hub', 'Meinert "m2j" Jordan', 'Ian Knightly' 'Matthias "matthub" Hub', 'Meinert "m2j" Jordan', 'Ian Knightly'
'Armin "orangeshirt" K\xf6hler', 'Armin "orangeshirt" K\xf6hler',
'Rafael "rafaellerm" Lerm', 'Gabriel loo', 'Erik "luen" Lundin', 'Edwin "edwinlunando" Lunando', 'Rafael "rafaellerm" Lerm', 'Gabriel loo', 'Erik "luen" Lundin', 'Edwin "edwinlunando" Lunando',

View File

@ -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.

View File

@ -53,10 +53,6 @@ __default_settings__ = {
'bibles/display new chapter': False, 'bibles/display new chapter': False,
'bibles/second bibles': True, 'bibles/second bibles': True,
'bibles/primary bible': '', 'bibles/primary bible': '',
'bibles/proxy name': '',
'bibles/proxy address': '',
'bibles/proxy username': '',
'bibles/proxy password': '',
'bibles/bible theme': '', 'bibles/bible theme': '',
'bibles/verse separator': '', 'bibles/verse separator': '',
'bibles/range separator': '', 'bibles/range separator': '',

View File

@ -212,22 +212,22 @@ class BibleImportForm(OpenLPWizard):
self.open_song_layout.addRow(self.open_song_file_label, self.open_song_path_edit) 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.open_song_layout.setItem(1, QtWidgets.QFormLayout.LabelRole, self.spacer)
self.select_stack.addWidget(self.open_song_widget) self.select_stack.addWidget(self.open_song_widget)
self.web_tab_widget = QtWidgets.QTabWidget(self.select_page) self.web_widget = QtWidgets.QWidget(self.select_page)
self.web_tab_widget.setObjectName('WebTabWidget') self.web_widget.setObjectName('WebWidget')
self.web_bible_tab = QtWidgets.QWidget() self.web_bible_tab = QtWidgets.QWidget()
self.web_bible_tab.setObjectName('WebBibleTab') 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_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_update_label.setObjectName('WebUpdateLabel')
self.web_bible_layout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.web_update_label) 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_update_button.setObjectName('WebUpdateButton')
self.web_bible_layout.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.web_update_button) 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_source_label.setObjectName('WebSourceLabel')
self.web_bible_layout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.web_source_label) 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.setObjectName('WebSourceComboBox')
self.web_source_combo_box.addItems(['', '', '']) self.web_source_combo_box.addItems(['', '', ''])
self.web_source_combo_box.setEnabled(False) self.web_source_combo_box.setEnabled(False)
@ -245,31 +245,7 @@ class BibleImportForm(OpenLPWizard):
self.web_progress_bar.setObjectName('WebTranslationProgressBar') self.web_progress_bar.setObjectName('WebTranslationProgressBar')
self.web_progress_bar.setVisible(False) self.web_progress_bar.setVisible(False)
self.web_bible_layout.setWidget(3, QtWidgets.QFormLayout.SpanningRole, self.web_progress_bar) self.web_bible_layout.setWidget(3, QtWidgets.QFormLayout.SpanningRole, self.web_progress_bar)
self.web_tab_widget.addTab(self.web_bible_tab, '') self.select_stack.addWidget(self.web_widget)
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.zefania_widget = QtWidgets.QWidget(self.select_page) self.zefania_widget = QtWidgets.QWidget(self.select_page)
self.zefania_widget.setObjectName('ZefaniaWidget') self.zefania_widget.setObjectName('ZefaniaWidget')
self.zefania_layout = QtWidgets.QFormLayout(self.zefania_widget) self.zefania_layout = QtWidgets.QFormLayout(self.zefania_widget)
@ -429,14 +405,6 @@ class BibleImportForm(OpenLPWizard):
self.web_source_combo_box.setItemText(WebDownload.Bibleserver, translate('BiblesPlugin.ImportWizardForm', self.web_source_combo_box.setItemText(WebDownload.Bibleserver, translate('BiblesPlugin.ImportWizardForm',
'Bibleserver')) 'Bibleserver'))
self.web_translation_label.setText(translate('BiblesPlugin.ImportWizardForm', 'Bible:')) 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_bible_label.setText(translate('BiblesPlugin.ImportWizardForm', 'Bibles:'))
self.sword_folder_label.setText(translate('BiblesPlugin.ImportWizardForm', 'SWORD data folder:')) self.sword_folder_label.setText(translate('BiblesPlugin.ImportWizardForm', 'SWORD data folder:'))
self.sword_zipfile_label.setText(translate('BiblesPlugin.ImportWizardForm', 'SWORD zip-file:')) self.sword_zipfile_label.setText(translate('BiblesPlugin.ImportWizardForm', 'SWORD zip-file:'))
@ -611,11 +579,10 @@ class BibleImportForm(OpenLPWizard):
self.web_update_button.setEnabled(False) self.web_update_button.setEnabled(False)
self.web_progress_bar.setVisible(True) self.web_progress_bar.setVisible(True)
self.web_progress_bar.setValue(0) self.web_progress_bar.setValue(0)
proxy_server = self.field('proxy_server')
# TODO: Where does critical_error_message_box get %s string from? # TODO: Where does critical_error_message_box get %s string from?
for (download_type, extractor) in ((WebDownload.Crosswalk, CWExtract(proxy_server)), for (download_type, extractor) in ((WebDownload.Crosswalk, CWExtract()),
(WebDownload.BibleGateway, BGExtract(proxy_server)), (WebDownload.BibleGateway, BGExtract()),
(WebDownload.Bibleserver, BSExtract(proxy_server))): (WebDownload.Bibleserver, BSExtract())):
try: try:
bibles = extractor.get_bibles_from_http() bibles = extractor.get_bibles_from_http()
except (urllib.error.URLError, ConnectionError) as err: except (urllib.error.URLError, ConnectionError) as err:
@ -681,9 +648,6 @@ class BibleImportForm(OpenLPWizard):
self.select_page.registerField('web_biblename', self.web_translation_combo_box) 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_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('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_version', self.version_name_edit)
self.license_details_page.registerField('license_copyright', self.copyright_edit) self.license_details_page.registerField('license_copyright', self.copyright_edit)
self.license_details_page.registerField('license_permissions', self.permissions_edit) self.license_details_page.registerField('license_permissions', self.permissions_edit)
@ -708,9 +672,6 @@ class BibleImportForm(OpenLPWizard):
self.setField('sword_zip_path', None) self.setField('sword_zip_path', None)
self.setField('web_location', WebDownload.Crosswalk) self.setField('web_location', WebDownload.Crosswalk)
self.setField('web_biblename', self.web_translation_combo_box.currentIndex()) 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.setField('license_version', self.version_name_edit.text())
self.version_name_edit.setPlaceholderText(UiStrings().RequiredShowInFooter) self.version_name_edit.setPlaceholderText(UiStrings().RequiredShowInFooter)
self.setField('license_copyright', self.copyright_edit.text()) self.setField('license_copyright', self.copyright_edit.text())
@ -767,9 +728,6 @@ class BibleImportForm(OpenLPWizard):
BibleFormat.WebDownload, name=license_version, BibleFormat.WebDownload, name=license_version,
download_source=WebDownload.Names[download_location], download_source=WebDownload.Names[download_location],
download_name=bible, 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 language_id=language_id
) )
elif bible_type == BibleFormat.Zefania: elif bible_type == BibleFormat.Zefania:

View File

@ -83,20 +83,19 @@ def init_schema(url):
meta_table = Table('metadata', metadata, meta_table = Table('metadata', metadata,
Column('key', types.Unicode(255), primary_key=True, index=True), 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, book_table = Table('book', metadata,
Column('id', types.Integer, primary_key=True), Column('id', types.Integer, primary_key=True),
Column('book_reference_id', types.Integer, index=True), Column('book_reference_id', types.Integer, index=True),
Column('testament_reference_id', types.Integer), 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, verse_table = Table('verse', metadata,
Column('id', types.Integer, primary_key=True, index=True), Column('id', types.Integer, primary_key=True, index=True),
Column('book_id', types.Integer, ForeignKey( Column('book_id', types.Integer, ForeignKey('book.id'), index=True),
'book.id'), index=True),
Column('chapter', types.Integer, index=True), Column('chapter', types.Integer, index=True),
Column('verse', types.Integer, index=True), Column('verse', types.Integer, index=True),
Column('text', types.UnicodeText, index=True),) Column('text', types.UnicodeText, index=True))
try: try:
class_mapper(BibleMeta) class_mapper(BibleMeta)

View File

@ -95,9 +95,8 @@ class BGExtract(RegistryProperties):
""" """
NAME = 'BibleGateway' NAME = 'BibleGateway'
def __init__(self, proxy_url=None): def __init__(self):
log.debug('BGExtract.init(proxy_url="{url}")'.format(url=proxy_url)) log.debug('BGExtract.__init__()')
self.proxy_url = proxy_url
socket.setdefaulttimeout(30) socket.setdefaulttimeout(30)
def _remove_elements(self, parent, tag, class_=None): def _remove_elements(self, parent, tag, class_=None):
@ -359,9 +358,8 @@ class BSExtract(RegistryProperties):
""" """
NAME = 'BibleServer' NAME = 'BibleServer'
def __init__(self, proxy_url=None): def __init__(self):
log.debug('BSExtract.init("{url}")'.format(url=proxy_url)) log.debug('BSExtract.__init__()')
self.proxy_url = proxy_url
socket.setdefaulttimeout(30) socket.setdefaulttimeout(30)
def get_bible_chapter(self, version, book_name, chapter): def get_bible_chapter(self, version, book_name, chapter):
@ -462,9 +460,8 @@ class CWExtract(RegistryProperties):
""" """
NAME = 'Crosswalk' NAME = 'Crosswalk'
def __init__(self, proxy_url=None): def __init__(self):
log.debug('CWExtract.init("{url}")'.format(url=proxy_url)) log.debug('CWExtract.__init__()')
self.proxy_url = proxy_url
socket.setdefaulttimeout(30) socket.setdefaulttimeout(30)
def get_bible_chapter(self, version, book_name, chapter): def get_bible_chapter(self, version, book_name, chapter):
@ -596,19 +593,9 @@ class HTTPBible(BibleImport, RegistryProperties):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.download_source = kwargs['download_source'] self.download_source = kwargs['download_source']
self.download_name = kwargs['download_name'] 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 self.language_id = None
if 'path' in kwargs: if 'path' in kwargs:
self.path = kwargs['path'] 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: if 'language_id' in kwargs:
self.language_id = kwargs['language_id'] self.language_id = kwargs['language_id']
@ -622,20 +609,12 @@ class HTTPBible(BibleImport, RegistryProperties):
'Registering Bible and loading books...')) 'Registering Bible and loading books...'))
self.save_meta('download_source', self.download_source) self.save_meta('download_source', self.download_source)
self.save_meta('download_name', self.download_name) 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': if self.download_source.lower() == 'crosswalk':
handler = CWExtract(self.proxy_server) handler = CWExtract()
elif self.download_source.lower() == 'biblegateway': elif self.download_source.lower() == 'biblegateway':
handler = BGExtract(self.proxy_server) handler = BGExtract()
elif self.download_source.lower() == 'bibleserver': elif self.download_source.lower() == 'bibleserver':
handler = BSExtract(self.proxy_server) handler = BSExtract()
books = handler.get_books_from_http(self.download_name) books = handler.get_books_from_http(self.download_name)
if not books: if not books:
log.error('Importing books from {source} - download name: "{name}" ' log.error('Importing books from {source} - download name: "{name}" '
@ -723,11 +702,11 @@ class HTTPBible(BibleImport, RegistryProperties):
log.debug('HTTPBible.get_chapter("{book}", "{chapter}")'.format(book=book, chapter=chapter)) log.debug('HTTPBible.get_chapter("{book}", "{chapter}")'.format(book=book, chapter=chapter))
log.debug('source = {source}'.format(source=self.download_source)) log.debug('source = {source}'.format(source=self.download_source))
if self.download_source.lower() == 'crosswalk': if self.download_source.lower() == 'crosswalk':
handler = CWExtract(self.proxy_server) handler = CWExtract()
elif self.download_source.lower() == 'biblegateway': elif self.download_source.lower() == 'biblegateway':
handler = BGExtract(self.proxy_server) handler = BGExtract()
elif self.download_source.lower() == 'bibleserver': elif self.download_source.lower() == 'bibleserver':
handler = BSExtract(self.proxy_server) handler = BSExtract()
return handler.get_bible_chapter(self.download_name, book, chapter) return handler.get_bible_chapter(self.download_name, book, chapter)
def get_books(self): def get_books(self):

View File

@ -118,7 +118,6 @@ class BibleManager(LogMixin, RegistryProperties):
self.web = 'Web' self.web = 'Web'
self.db_cache = None self.db_cache = None
self.path = AppLocation.get_section_data_path(self.settings_section) self.path = AppLocation.get_section_data_path(self.settings_section)
self.proxy_name = Settings().value(self.settings_section + '/proxy name')
self.suffix = '.sqlite' self.suffix = '.sqlite'
self.import_wizard = None self.import_wizard = None
self.reload_bibles() self.reload_bibles()
@ -151,11 +150,8 @@ class BibleManager(LogMixin, RegistryProperties):
if self.db_cache[name].is_web_bible: if self.db_cache[name].is_web_bible:
source = self.db_cache[name].get_object(BibleMeta, 'download_source') source = self.db_cache[name].get_object(BibleMeta, 'download_source')
download_name = self.db_cache[name].get_object(BibleMeta, 'download_name').value 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, web_bible = HTTPBible(self.parent, path=self.path, file=file_path, download_source=source.value,
download_name=download_name) download_name=download_name)
if meta_proxy:
web_bible.proxy_server = meta_proxy.value
self.db_cache[name] = web_bible self.db_cache[name] = web_bible
log.debug('Bibles reloaded') log.debug('Bibles reloaded')

View File

@ -24,9 +24,17 @@ The :mod:`upgrade` module provides a way for the database and schema that is the
""" """
import logging 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__) log = logging.getLogger(__name__)
__version__ = 1 __version__ = 2
# TODO: When removing an upgrade path the ftw-data needs updating to the minimum supported version # TODO: When removing an upgrade path the ftw-data needs updating to the minimum supported version
@ -37,3 +45,48 @@ def upgrade_1(session, metadata):
This upgrade renamed a number of keys to a single naming convention. This upgrade renamed a number of keys to a single naming convention.
""" """
log.info('No upgrades to perform') 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)
"""
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}.<br>'
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'))

View File

@ -58,6 +58,7 @@ class Ui_ChooseGroupDialog(object):
self.choose_group_layout.setWidget(3, QtWidgets.QFormLayout.LabelRole, self.existing_radio_button) self.choose_group_layout.setWidget(3, QtWidgets.QFormLayout.LabelRole, self.existing_radio_button)
self.group_combobox = QtWidgets.QComboBox(choose_group_dialog) self.group_combobox = QtWidgets.QComboBox(choose_group_dialog)
self.group_combobox.setObjectName('group_combobox') self.group_combobox.setObjectName('group_combobox')
self.group_combobox.activated.connect(self.on_group_combobox_selected)
self.choose_group_layout.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.group_combobox) self.choose_group_layout.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.group_combobox)
self.new_radio_button = QtWidgets.QRadioButton(choose_group_dialog) self.new_radio_button = QtWidgets.QRadioButton(choose_group_dialog)
self.new_radio_button.setChecked(False) self.new_radio_button.setChecked(False)
@ -65,6 +66,7 @@ class Ui_ChooseGroupDialog(object):
self.choose_group_layout.setWidget(4, QtWidgets.QFormLayout.LabelRole, self.new_radio_button) self.choose_group_layout.setWidget(4, QtWidgets.QFormLayout.LabelRole, self.new_radio_button)
self.new_group_edit = QtWidgets.QLineEdit(choose_group_dialog) self.new_group_edit = QtWidgets.QLineEdit(choose_group_dialog)
self.new_group_edit.setObjectName('new_group_edit') self.new_group_edit.setObjectName('new_group_edit')
self.new_group_edit.textEdited.connect(self.on_new_group_edit_changed)
self.choose_group_layout.setWidget(4, QtWidgets.QFormLayout.FieldRole, self.new_group_edit) self.choose_group_layout.setWidget(4, QtWidgets.QFormLayout.FieldRole, self.new_group_edit)
self.group_button_box = create_button_box(choose_group_dialog, 'buttonBox', ['ok']) self.group_button_box = create_button_box(choose_group_dialog, 'buttonBox', ['ok'])
self.choose_group_layout.setWidget(5, QtWidgets.QFormLayout.FieldRole, self.group_button_box) self.choose_group_layout.setWidget(5, QtWidgets.QFormLayout.FieldRole, self.group_button_box)
@ -83,3 +85,22 @@ class Ui_ChooseGroupDialog(object):
self.nogroup_radio_button.setText(translate('ImagePlugin.ChooseGroupForm', 'No group')) self.nogroup_radio_button.setText(translate('ImagePlugin.ChooseGroupForm', 'No group'))
self.existing_radio_button.setText(translate('ImagePlugin.ChooseGroupForm', 'Existing group')) self.existing_radio_button.setText(translate('ImagePlugin.ChooseGroupForm', 'Existing group'))
self.new_radio_button.setText(translate('ImagePlugin.ChooseGroupForm', 'New group')) self.new_radio_button.setText(translate('ImagePlugin.ChooseGroupForm', 'New group'))
def on_group_combobox_selected(self, index):
"""
Handles the activated signal from the existing group combobox when the
user makes a selection
:param index: position of the selected item in the combobox
"""
self.existing_radio_button.setChecked(True)
self.group_combobox.setFocus()
def on_new_group_edit_changed(self, new_group):
"""
Handles the textEdited signal from the new group text input field
when the user enters a new group name
:param new_group: new text entered by the user
"""
self.new_radio_button.setChecked(True)

View File

@ -48,4 +48,5 @@ class ChooseGroupForm(QtWidgets.QDialog, Ui_ChooseGroupDialog):
for index in range(self.group_combobox.count()): for index in range(self.group_combobox.count()):
if self.group_combobox.itemData(index) == selected_group: if self.group_combobox.itemData(index) == selected_group:
self.group_combobox.setCurrentIndex(index) self.group_combobox.setCurrentIndex(index)
self.existing_radio_button.setChecked(True)
return QtWidgets.QDialog.exec(self) return QtWidgets.QDialog.exec(self)

View File

@ -430,16 +430,6 @@ class ImageMediaItem(MediaManagerItem):
if isinstance(selected_item.data(0, QtCore.Qt.UserRole), ImageGroups): if isinstance(selected_item.data(0, QtCore.Qt.UserRole), ImageGroups):
preselect_group = selected_item.data(0, QtCore.Qt.UserRole).id preselect_group = selected_item.data(0, QtCore.Qt.UserRole).id
# Enable and disable parts of the 'choose group' form # Enable and disable parts of the 'choose group' form
if preselect_group is None:
self.choose_group_form.nogroup_radio_button.setChecked(True)
self.choose_group_form.nogroup_radio_button.setFocus()
self.choose_group_form.existing_radio_button.setChecked(False)
self.choose_group_form.new_radio_button.setChecked(False)
else:
self.choose_group_form.nogroup_radio_button.setChecked(False)
self.choose_group_form.existing_radio_button.setChecked(True)
self.choose_group_form.new_radio_button.setChecked(False)
self.choose_group_form.group_combobox.setFocus()
if self.manager.get_object_count(ImageGroups) == 0: if self.manager.get_object_count(ImageGroups) == 0:
self.choose_group_form.existing_radio_button.setDisabled(True) self.choose_group_form.existing_radio_button.setDisabled(True)
self.choose_group_form.group_combobox.setDisabled(True) self.choose_group_form.group_combobox.setDisabled(True)

View File

@ -44,7 +44,7 @@ def set_up_fault_handling():
faulthandler.enable((AppLocation.get_directory(AppLocation.CacheDir) / 'error.log').open('wb')) faulthandler.enable((AppLocation.get_directory(AppLocation.CacheDir) / 'error.log').open('wb'))
if __name__ == '__main__': def start():
""" """
Instantiate and run the application. Instantiate and run the application.
""" """
@ -60,3 +60,7 @@ if __name__ == '__main__':
if is_macosx(): if is_macosx():
sys.argv = [x for x in sys.argv if not x.startswith('-psn')] sys.argv = [x for x in sys.argv if not x.startswith('-psn')]
main() main()
if __name__ == '__main__':
start()

View File

@ -11,11 +11,8 @@ environment:
install: install:
# Install dependencies from pypi # 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" - "%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"
# Install mysql dependency # Download and install pyicu (originally from http://www.lfd.uci.edu/~gohlke/pythonlibs/)
- "%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 https://get.openlp.org/win-sdk/PyICU-1.9.5-cp34-cp34m-win32.whl" - "%PYTHON%\\python.exe -m pip install https://get.openlp.org/win-sdk/PyICU-1.9.5-cp34-cp34m-win32.whl"
# Download and install PyQt5 # 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 - 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

View File

@ -33,25 +33,18 @@ import os
import sys import sys
from distutils.version import LooseVersion 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_WIN = sys.platform.startswith('win')
IS_LIN = sys.platform.startswith('lin') IS_LIN = sys.platform.startswith('lin')
IS_MAC = sys.platform.startswith('dar') IS_MAC = sys.platform.startswith('dar')
VERS = { VERS = {
'Python': '3.4', 'Python': '3.6',
'PyQt5': '5.0', 'PyQt5': '5.0',
'Qt5': '5.0', 'Qt5': '5.0',
'pymediainfo': '2.2',
'sqlalchemy': '0.5', 'sqlalchemy': '0.5',
# pyenchant 1.6 required on Windows 'enchant': '1.6'
'enchant': '1.6' if IS_WIN else '1.3'
} }
# pywin32 # pywin32
@ -59,7 +52,6 @@ WIN32_MODULES = [
'win32com', 'win32com',
'win32ui', 'win32ui',
'pywintypes', 'pywintypes',
'pyodbc',
'icu', 'icu',
] ]
@ -85,19 +77,16 @@ MODULES = [
'PyQt5.QtTest', 'PyQt5.QtTest',
'PyQt5.QtWebKit', 'PyQt5.QtWebKit',
'PyQt5.QtMultimedia', 'PyQt5.QtMultimedia',
'pymediainfo',
'appdirs',
'sqlalchemy', 'sqlalchemy',
'alembic', 'alembic',
'sqlite3',
'lxml', 'lxml',
'chardet', 'chardet',
'enchant',
'bs4', 'bs4',
'mako', 'mako',
'uno',
'websockets', 'websockets',
'asyncio',
'waitress', 'waitress',
'six',
'webob', 'webob',
'requests', 'requests',
'qtawesome' 'qtawesome'
@ -105,12 +94,17 @@ MODULES = [
OPTIONAL_MODULES = [ OPTIONAL_MODULES = [
('mysql.connector', '(MySQL support)', True), ('mysql.connector', '(MySQL support)'),
('psycopg2', '(PostgreSQL support)', True), ('pyodbc', '(ODBC support)'),
('nose2', '(testing framework)', True), ('psycopg2', '(PostgreSQL support)'),
('mock', '(testing module)', sys.version_info[1] < 3), ('enchant', '(spell checker)'),
('jenkins', '(access jenkins api - package name: jenkins-webapi)', True), ('pysword', '(import SWORD bibles)'),
('pysword', '(import SWORD bibles)', True), ('uno', '(LibreOffice/OpenOffice support)'),
# development/testing modules
('jenkins', '(access jenkins api)'),
('launchpadlib', '(launchpad script support)'),
('nose2', '(testing framework)'),
('pylint', '(linter)')
] ]
w = sys.stdout.write w = sys.stdout.write
@ -239,8 +233,7 @@ def main():
check_module(m) check_module(m)
print('Checking for optional modules...') print('Checking for optional modules...')
for m in 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: if IS_WIN:
print('Checking for Windows specific modules...') print('Checking for Windows specific modules...')
for m in WIN32_MODULES: for m in WIN32_MODULES:

View File

@ -1,3 +1,4 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 # vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
@ -21,9 +22,10 @@
############################################################################### ###############################################################################
import re import re
from subprocess import PIPE, Popen import sys
from subprocess import Popen, PIPE
from setuptools import find_packages, setup from setuptools import setup, find_packages
VERSION_FILE = 'openlp/.version' VERSION_FILE = 'openlp/.version'
@ -110,6 +112,34 @@ except Exception:
finally: finally:
ver_file.close() ver_file.close()
requires = [
'alembic',
'appdirs',
'beautifulsoup4',
'chardet',
'lxml',
'Mako',
'pymediainfo >= 2.2',
'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( setup(
name='OpenLP', name='OpenLP',
@ -157,18 +187,25 @@ using a computer and a data projector.""",
keywords='open source church presentation lyrics projection song bible display project', keywords='open source church presentation lyrics projection song bible display project',
author='Raoul Snyman', author='Raoul Snyman',
author_email='raoulsnyman@openlp.org', author_email='raoulsnyman@openlp.org',
url='http://openlp.org/', url='https://openlp.org/',
license='GNU General Public License', license='GNU General Public License',
packages=find_packages(exclude=['ez_setup', 'examples', 'tests']), packages=find_packages(exclude=['ez_setup', 'tests']),
scripts=['openlp.py'], py_modules=['run_openlp'],
include_package_data=True, include_package_data=True,
zip_safe=False, zip_safe=False,
install_requires=[ python_requires='>=3.6',
# -*- Extra requirements: -*- install_requires=requires,
'sqlalchemy', extras_require={
'alembic' 'mysql': ['mysql-connector-python'],
], 'odbc': ['pyodbc'],
entry_points=""" 'postgresql': ['psycopg2'],
# -*- Entry points: -*- 'spellcheck': ['pyenchant >= 1.6'],
""" 'sword-bibles': ['pysword'],
# Required for scripts/*.py:
'jenkins': ['python-jenkins'],
'launchpad': ['launchpadlib']
},
tests_require=['nose2', 'PyICU', 'pylint', 'pyodbc', 'pysword'],
test_suite='nose2.collector.collector',
entry_points={'gui_scripts': ['openlp = run_openlp:start']}
) )

View File

@ -8,10 +8,10 @@ Prerequisites
In order to run the unit tests, you will need the following Python packages/libraries installed: In order to run the unit tests, you will need the following Python packages/libraries installed:
- Mock - pytest
- Nose - pylint3
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 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. packages. On Windows and Mac OS X you will need to use ``pip`` or ``easy_install`` to install these packages.
Running the Tests 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:: To run the tests, navigate to the root directory of the OpenLP project, and then run the following command::
nosetests -v tests pytest -v tests
Or, to run only the functional tests, run the following command:: Or, to run only the functional tests, run the following command::
nosetests -v tests/functional pytest -v tests/functional
Or, to run only a particular test suite within a file, run the following command:: Or, to run only a particular test suite within a file, run the following command::
nosetests -v tests/functional/test_applocation.py pytest -v tests/functional/openlp_core/test_app.py
Finally, to only run a particular test, run the following command::
nosetests -v tests/functional/test_applocation.py:TestAppLocation.get_frozen_path_test

View File

@ -162,6 +162,7 @@ def test_check_same_instance():
def test_get_language_from_settings(): def test_get_language_from_settings():
assert LanguageManager.get_language() == 'en' assert LanguageManager.get_language() == 'en'
def test_get_language_from_settings_returns_unchanged_if_unknown_format(): def test_get_language_from_settings_returns_unchanged_if_unknown_format():
Settings().setValue('core/language', '(foobar)') Settings().setValue('core/language', '(foobar)')
assert LanguageManager.get_language() == '(foobar)' assert LanguageManager.get_language() == '(foobar)'

View File

@ -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)

View File

@ -0,0 +1,218 @@
# -*- 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)]

View File

@ -0,0 +1,21 @@
# -*- 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 #
###############################################################################

View File

@ -0,0 +1,103 @@
# -*- 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 #
###############################################################################
"""
Tests for choosegroupform from the openlp.plugins.images.forms package.
"""
from unittest import TestCase
from unittest.mock import MagicMock
from PyQt5 import QtWidgets
from openlp.core.common.registry import Registry
from openlp.plugins.images.forms.choosegroupform import ChooseGroupForm
from tests.helpers.testmixin import TestMixin
class TestImageChooseGroupForm(TestCase, TestMixin):
"""
Test the ChooseGroupForm class
"""
def setUp(self):
"""
Create the UI
"""
Registry.create()
self.setup_application()
self.main_window = QtWidgets.QMainWindow()
Registry().register('main_window', self.main_window)
self.form = ChooseGroupForm(self.main_window)
def tearDown(self):
"""
Cleanup
"""
del self.form
del self.main_window
def test_no_group_selected_by_default(self):
"""
Tests that the No Group option is the default selection
"""
assert self.form.nogroup_radio_button.isChecked()
def test_provided_group_is_selected(self):
"""
Tests preselected group initialization
"""
# GIVEN: There are some existing groups
QtWidgets.QDialog.exec = MagicMock(return_value=QtWidgets.QDialog.Accepted)
self.form.group_combobox.addItem('Group 1', 0)
self.form.group_combobox.addItem('Group 2', 1)
# WHEN: The form is displayed with preselected group index 1
self.form.exec(1)
# THEN: The Existing Group should be selected along with the radio button
assert self.form.group_combobox.currentIndex() == 1
assert self.form.existing_radio_button.isChecked()
def test_auto_select_existing_group_on_combo_selection(self):
"""
Tests that the Existing Group option becomes selected when changing the combobox
"""
# GIVEN: No preselected group was provided during initialization
assert not self.form.existing_radio_button.isChecked()
# WHEN: An existing group is selected from the combo box
self.form.on_group_combobox_selected(0)
# THEN: The Existing Group radio button should also be selected
assert self.form.existing_radio_button.isChecked()
def test_auto_select_new_group_on_edit(self):
"""
Tests that the New Group option becomes selected when changing the text field
"""
# GIVEN: The New Group option has not already been selected
assert not self.form.new_radio_button.isChecked()
# WHEN: The user enters text into the new group name text field
self.form.on_new_group_edit_changed('Test Group')
# THEN: The New Group radio button should also be selected
assert self.form.new_radio_button.isChecked()

View File

@ -0,0 +1,223 @@
# -*- 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"

View File

@ -4,7 +4,7 @@
############################################################################### ###############################################################################
# OpenLP - Open Source Lyrics Projection # # 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 # # 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 # # 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 # # 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
""" """

View File

@ -28,15 +28,45 @@ from unittest import TestCase
from unittest.mock import call, patch from unittest.mock import call, patch
import openlp.core.projectors.pjlink import openlp.core.projectors.pjlink
from openlp.core.common.registry import Registry
from openlp.core.projectors.constants import PJLINK_PORT from openlp.core.projectors.constants import PJLINK_PORT
from openlp.core.projectors.pjlink import PJLinkUDP from openlp.core.projectors.pjlink import PJLinkUDP
from openlp.core.projectors.tab import ProjectorTab
from tests.helpers.testmixin import TestMixin
from tests.resources.projector.data import TEST1_DATA from tests.resources.projector.data import TEST1_DATA
class TestPJLinkBase(TestCase): class TestPJLinkBase(TestCase, TestMixin):
""" """
Tests for the PJLinkUDP class 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') @patch.object(openlp.core.projectors.pjlink, 'log')
def test_get_datagram_data_negative_zero_length(self, mock_log): def test_get_datagram_data_negative_zero_length(self, mock_log):
""" """
@ -44,9 +74,9 @@ class TestPJLinkBase(TestCase):
""" """
# GIVEN: Test setup # GIVEN: Test setup
pjlink_udp = PJLinkUDP() pjlink_udp = PJLinkUDP()
log_warning_calls = [call('(UDP) No data (-1)')] log_warning_calls = [call('(UDP:4352) No data (-1)')]
log_debug_calls = [call('(UDP) PJLinkUDP() Initialized for port 4352'), log_debug_calls = [call('(UDP:4352) PJLinkUDP() Initialized'),
call('(UDP) get_datagram() - Receiving data')] call('(UDP:4352) get_datagram() - Receiving data')]
with patch.object(pjlink_udp, 'pendingDatagramSize') as mock_datagram, \ with patch.object(pjlink_udp, 'pendingDatagramSize') as mock_datagram, \
patch.object(pjlink_udp, 'readDatagram') as mock_read: patch.object(pjlink_udp, 'readDatagram') as mock_read:
mock_datagram.return_value = -1 mock_datagram.return_value = -1
@ -66,9 +96,9 @@ class TestPJLinkBase(TestCase):
""" """
# GIVEN: Test setup # GIVEN: Test setup
pjlink_udp = PJLinkUDP() pjlink_udp = PJLinkUDP()
log_warning_calls = [call('(UDP) get_datagram() called when pending data size is 0')] log_warning_calls = [call('(UDP:4352) get_datagram() called when pending data size is 0')]
log_debug_calls = [call('(UDP) PJLinkUDP() Initialized for port 4352'), log_debug_calls = [call('(UDP:4352) PJLinkUDP() Initialized'),
call('(UDP) get_datagram() - Receiving data')] call('(UDP:4352) get_datagram() - Receiving data')]
with patch.object(pjlink_udp, 'pendingDatagramSize') as mock_datagram, \ with patch.object(pjlink_udp, 'pendingDatagramSize') as mock_datagram, \
patch.object(pjlink_udp, 'readDatagram') as mock_read: patch.object(pjlink_udp, 'readDatagram') as mock_read:
mock_datagram.return_value = 0 mock_datagram.return_value = 0
@ -88,9 +118,9 @@ class TestPJLinkBase(TestCase):
""" """
# GIVEN: Test setup # GIVEN: Test setup
pjlink_udp = PJLinkUDP() pjlink_udp = PJLinkUDP()
log_warning_calls = [call('(UDP) get_datagram() called when pending data size is 0')] log_warning_calls = [call('(UDP:4352) get_datagram() called when pending data size is 0')]
log_debug_calls = [call('(UDP) PJLinkUDP() Initialized for port 4352'), log_debug_calls = [call('(UDP:4352) PJLinkUDP() Initialized'),
call('(UDP) get_datagram() - Receiving data')] call('(UDP:4352) get_datagram() - Receiving data')]
with patch.object(pjlink_udp, 'pendingDatagramSize') as mock_datagram: with patch.object(pjlink_udp, 'pendingDatagramSize') as mock_datagram:
mock_datagram.return_value = 0 mock_datagram.return_value = 0
@ -100,3 +130,147 @@ class TestPJLinkBase(TestCase):
# THEN: Log entries should be made and method returns # THEN: Log entries should be made and method returns
mock_log.warning.assert_has_calls(log_warning_calls) mock_log.warning.assert_has_calls(log_warning_calls)
mock_log.debug.assert_has_calls(log_debug_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)

Binary file not shown.

Binary file not shown.