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
"""
# Set up command line options.
parser = argparse.ArgumentParser(prog='openlp.py')
parser = argparse.ArgumentParser(prog='openlp')
parser.add_argument('-e', '--no-error-form', dest='no_error_form', action='store_true',
help='Disable the error notification form.')
parser.add_argument('-l', '--log-level', dest='loglevel', default='warning', metavar='LEVEL',

View File

@ -60,7 +60,6 @@ def get_local_ip4():
:returns: Dict of interfaces
"""
# Get the local IPv4 active address(es) that are NOT localhost (lo or '127.0.0.1')
log.debug('Getting local IPv4 interface(es) information')
my_ip4 = {}
for iface in QNetworkInterface.allInterfaces():
@ -70,8 +69,6 @@ def get_local_ip4():
log.debug('Checking address(es) protocol')
for address in iface.addressEntries():
ip = address.ip()
# NOTE: Next line will skip if interface is localhost - keep for now until we decide about it later
# if (ip.protocol() == QAbstractSocket.IPv4Protocol) and (ip != QHostAddress.LocalHost):
log.debug('Checking for protocol == IPv4Protocol')
if ip.protocol() == QAbstractSocket.IPv4Protocol:
log.debug('Getting interface information')
@ -83,12 +80,13 @@ def get_local_ip4():
ip.toIPv4Address()).toString()
}
log.debug('Adding {iface} to active list'.format(iface=iface.name()))
if len(my_ip4) == 0:
log.warning('No active IPv4 network interfaces detected')
return my_ip4
if 'localhost' in my_ip4:
log.debug('Renaming windows localhost to lo')
my_ip4['lo'] = my_ip4['localhost']
my_ip4.pop('localhost')
if len(my_ip4) == 0:
log.warning('No active IPv4 network interfaces detected')
if len(my_ip4) == 1:
if 'lo' in my_ip4:
# No active interfaces - so leave localhost in there

View File

@ -26,19 +26,14 @@ import logging
import os
import sys
import appdirs
import openlp
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.settings import Settings
if not is_win() and not is_macosx():
try:
from xdg import BaseDirectory
XDG_BASE_AVAILABLE = True
except ImportError:
XDG_BASE_AVAILABLE = False
log = logging.getLogger(__name__)
FROZEN_APP_PATH = Path(sys.argv[0]).parent
@ -144,8 +139,10 @@ def _get_os_dir_path(dir_type):
elif dir_type == AppLocation.LanguageDir:
return Path(openlp.__file__).parent
return openlp_folder_path
elif is_macosx():
openlp_folder_path = Path(os.getenv('HOME'), 'Library', 'Application Support', 'openlp')
dirs = appdirs.AppDirs('openlp', multipath=True)
if is_macosx():
openlp_folder_path = Path(dirs.user_data_dir)
if dir_type == AppLocation.DataDir:
return openlp_folder_path / 'Data'
elif dir_type == AppLocation.LanguageDir:
@ -153,15 +150,15 @@ def _get_os_dir_path(dir_type):
return openlp_folder_path
else:
if dir_type == AppLocation.LanguageDir:
directory = Path('/usr', 'local', 'share', 'openlp')
site_dirs = dirs.site_data_dir.split(os.pathsep)
directory = Path(site_dirs[0])
if directory.exists():
return directory
return Path('/usr', 'share', 'openlp')
if XDG_BASE_AVAILABLE:
if dir_type == AppLocation.DataDir:
return Path(BaseDirectory.xdg_data_home, 'openlp')
elif dir_type == AppLocation.CacheDir:
return Path(BaseDirectory.xdg_cache_home, 'openlp')
return Path(site_dirs[1])
if dir_type == AppLocation.DataDir:
return Path(dirs.user_data_dir)
elif dir_type == AppLocation.CacheDir:
return Path(dirs.user_cache_dir)
if dir_type == AppLocation.DataDir:
return Path(os.getenv('HOME'), '.openlp', 'data')
return Path(os.getenv('HOME'), '.openlp')

View File

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

View File

@ -1,4 +1,4 @@
# -*- coding: utf-8 -*-
# -*- coding: utf-8 -*-
# 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/poll time': 20, # PJLink timeout is 30 seconds
'projector/socket timeout': 5, # 5 second socket timeout
'projector/source dialog type': 0 # Source select dialog box type
'projector/source dialog type': 0, # Source select dialog box type
'projector/udp broadcast listen': False # Enable/disable listening for PJLink 2 UDP broadcast packets
}
__file_path__ = ''
# Settings upgrades prior to 3.0
@ -306,7 +307,11 @@ class Settings(QtCore.QSettings):
('songuasge/db database', 'songusage/db database', []),
('presentations / Powerpoint Viewer', '', []),
(['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

View File

@ -399,4 +399,4 @@ def create_separated_list(string_list):
last=string_list[-1])
else:
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.
"""
log.debug('accept_me() signal received')
valid = True
if len(self.name_text.text().strip()) < 1:
QtWidgets.QMessageBox.warning(self,
translate('OpenLP.ProjectorEdit', 'Name Not Set'),

View File

@ -31,13 +31,14 @@ from PyQt5 import QtCore, QtGui, QtWidgets
from openlp.core.common.i18n import translate
from openlp.core.common.mixins import LogMixin, RegistryProperties
from openlp.core.common.registry import RegistryBase
from openlp.core.common.registry import Registry, RegistryBase
from openlp.core.common.settings import Settings
from openlp.core.lib.ui import create_widget_action
from openlp.core.projectors import DialogSourceStyle
from openlp.core.projectors.constants import E_AUTHENTICATION, E_ERROR, E_NETWORK, E_NOT_CONNECTED, E_SOCKET_TIMEOUT, \
E_UNKNOWN_SOCKET_ERROR, PJLINK_PORT, QSOCKET_STATE, S_CONNECTED, S_CONNECTING, S_COOLDOWN, S_INITIALIZE, \
S_NOT_CONNECTED, S_OFF, S_ON, S_STANDBY, S_WARMUP, STATUS_CODE, STATUS_MSG
from openlp.core.projectors.constants import E_AUTHENTICATION, E_ERROR, E_NETWORK, E_NOT_CONNECTED, E_SOCKET_TIMEOUT,\
E_UNKNOWN_SOCKET_ERROR, QSOCKET_STATE, S_CONNECTED, S_CONNECTING, S_COOLDOWN, S_INITIALIZE, S_NOT_CONNECTED, S_OFF,\
S_ON, S_STANDBY, S_WARMUP, STATUS_CODE, STATUS_MSG
from openlp.core.projectors.db import ProjectorDB
from openlp.core.projectors.editform import ProjectorEditForm
from openlp.core.projectors.pjlink import PJLink, PJLinkUDP
@ -297,7 +298,7 @@ class ProjectorManager(QtWidgets.QWidget, RegistryBase, UiProjectorManager, LogM
self.projector_list = []
self.source_select_form = None
# Dictionary of PJLinkUDP objects to listen for UDP broadcasts from PJLink 2+ projectors.
# Key is port number that projectors use
# Key is port number
self.pjlink_udp = {}
# Dict for matching projector status to display icon
self.status_icons = {
@ -335,10 +336,6 @@ class ProjectorManager(QtWidgets.QWidget, RegistryBase, UiProjectorManager, LogM
"""
Post-initialize setups.
"""
# Default PJLink port UDP socket
log.debug('Creating PJLinkUDP listener for default port {port}'.format(port=PJLINK_PORT))
self.pjlink_udp = {PJLINK_PORT: PJLinkUDP(port=PJLINK_PORT)}
self.pjlink_udp[PJLINK_PORT].bind(PJLINK_PORT)
# Set 1.5 second delay before loading all projectors
if self.autostart:
log.debug('Delaying 1.5 seconds before loading all projectors')
@ -351,6 +348,36 @@ class ProjectorManager(QtWidgets.QWidget, RegistryBase, UiProjectorManager, LogM
self.projector_form.editProjector.connect(self.edit_projector_from_wizard)
self.projector_list_widget.itemSelectionChanged.connect(self.update_icons)
def udp_listen_add(self, port):
"""
Add UDP broadcast listener
"""
if port in self.pjlink_udp:
log.warning('UDP Listener for port {port} already added - skipping'.format(port=port))
else:
log.debug('Adding UDP listener on port {port}'.format(port=port))
self.pjlink_udp[port] = PJLinkUDP(port=port)
Registry().execute('udp_broadcast_add', port=port, callback=self.pjlink_udp[port].check_settings)
def udp_listen_delete(self, port):
"""
Remove a UDP broadcast listener
"""
log.debug('Checking for UDP port {port} listener deletion'.format(port=port))
if port not in self.pjlink_udp:
log.warn('UDP listener for port {port} not there - skipping delete'.format(port=port))
return
keep_port = False
for item in self.projector_list:
if port == item.link.port:
keep_port = True
if keep_port:
log.warn('UDP listener for port {port} needed for other projectors - skipping delete'.format(port=port))
return
Registry().execute('udp_broadcast_remove', port=port)
del self.pjlink_udp[port]
log.debug('UDP listener for port {port} deleted'.format(port=port))
def get_settings(self):
"""
Retrieve the saved settings
@ -518,25 +545,22 @@ class ProjectorManager(QtWidgets.QWidget, RegistryBase, UiProjectorManager, LogM
except (AttributeError, TypeError):
pass
try:
projector.poll_timer.stop()
projector.poll_timer.timeout.disconnect(projector.link.poll_loop)
projector.link.poll_timer.stop()
projector.link.poll_timer.timeout.disconnect(projector.link.poll_loop)
except (AttributeError, TypeError):
pass
try:
projector.socket_timer.stop()
projector.socket_timer.timeout.disconnect(projector.link.socket_abort)
except (AttributeError, TypeError):
pass
# Disconnect signals from projector being deleted
try:
self.pjlink_udp[projector.link.port].data_received.disconnect(projector.link.get_buffer)
projector.link.socket_timer.stop()
projector.link.socket_timer.timeout.disconnect(projector.link.socket_abort)
except (AttributeError, TypeError):
pass
old_port = projector.link.port
# Rebuild projector list
new_list = []
for item in self.projector_list:
if item.link.db_item.id == projector.link.db_item.id:
log.debug('Removing projector "{item}"'.format(item=item.link.name))
continue
new_list.append(item)
self.projector_list = new_list
@ -546,6 +570,7 @@ class ProjectorManager(QtWidgets.QWidget, RegistryBase, UiProjectorManager, LogM
log.warning('Delete projector {item} failed'.format(item=projector.db_item))
for item in self.projector_list:
log.debug('New projector list - item: {ip} {name}'.format(ip=item.link.ip, name=item.link.name))
self.udp_listen_delete(old_port)
def on_disconnect_projector(self, opt=None):
"""
@ -748,15 +773,8 @@ class ProjectorManager(QtWidgets.QWidget, RegistryBase, UiProjectorManager, LogM
item.link.projectorAuthentication.connect(self.authentication_error)
item.link.projectorNoAuthentication.connect(self.no_authentication_error)
item.link.projectorUpdateIcons.connect(self.update_icons)
# Connect UDP signal to projector instances with same port
if item.link.port not in self.pjlink_udp:
log.debug('Adding new PJLinkUDP listener fo port {port}'.format(port=item.link.port))
self.pjlink_udp[item.link.port] = PJLinkUDP(port=item.link.port)
self.pjlink_udp[item.link.port].bind(item.link.port)
log.debug('Connecting PJLinkUDP port {port} signal to "{item}"'.format(port=item.link.port,
item=item.link.name))
self.pjlink_udp[item.link.port].data_received.connect(item.link.get_buffer)
# Add UDP listener for new projector port
self.udp_listen_add(item.link.port)
self.projector_list.append(item)
if start:
item.link.connect_to_host()
@ -783,13 +801,25 @@ class ProjectorManager(QtWidgets.QWidget, RegistryBase, UiProjectorManager, LogM
:param projector: Projector() instance of projector with updated information
"""
log.debug('edit_projector_from_wizard(ip={ip})'.format(ip=projector.ip))
old_port = self.old_projector.link.port
old_ip = self.old_projector.link.ip
self.old_projector.link.name = projector.name
self.old_projector.link.ip = projector.ip
self.old_projector.link.pin = None if projector.pin == '' else projector.pin
self.old_projector.link.port = projector.port
self.old_projector.link.location = projector.location
self.old_projector.link.notes = projector.notes
self.old_projector.widget.setText(projector.name)
self.old_projector.link.port = int(projector.port)
# Update projector list items
for item in self.projector_list:
if item.link.ip == old_ip:
item.link.port = int(projector.port)
# NOTE: This assumes (!) we are using IP addresses as keys
break
# Update UDP listeners before setting old_projector.port
if old_port != projector.port:
self.udp_listen_delete(old_port)
self.udp_listen_add(int(projector.port))
def _load_projectors(self):
"""'

View File

@ -99,32 +99,51 @@ class PJLinkUDP(QtNetwork.QUdpSocket):
self.search_active = False
self.search_time = 30000 # 30 seconds for allowed time
self.search_timer = QtCore.QTimer()
self.udp_broadcast_listen_setting = False
log.debug('(UDP:{port}) PJLinkUDP() Initialized'.format(port=self.port))
if Settings().value('projector/udp broadcast listen'):
self.udp_start()
def udp_start(self):
"""
Start listening on UDP port
"""
log.debug('(UDP:{port}) Start called'.format(port=self.port))
self.readyRead.connect(self.get_datagram)
log.debug('(UDP) PJLinkUDP() Initialized for port {port}'.format(port=self.port))
self.check_settings(checked=Settings().value('projector/udp broadcast listen'))
def udp_stop(self):
"""
Stop listening on UDP port
"""
log.debug('(UDP:{port}) Stopping listener'.format(port=self.port))
self.close()
self.readyRead.disconnect(self.get_datagram)
@QtCore.pyqtSlot()
def get_datagram(self):
"""
Retrieve packet and basic checks
"""
log.debug('(UDP) get_datagram() - Receiving data')
log.debug('(UDP:{port}) get_datagram() - Receiving data'.format(port=self.port))
read_size = self.pendingDatagramSize()
if -1 == read_size:
log.warning('(UDP) No data (-1)')
log.warning('(UDP:{port}) No data (-1)'.format(port=self.port))
return
elif 0 == read_size:
log.warning('(UDP) get_datagram() called when pending data size is 0')
log.warning('(UDP:{port}) get_datagram() called when pending data size is 0'.format(port=self.port))
return
elif read_size > PJLINK_MAX_PACKET:
log.warning('(UDP) UDP Packet too large ({size} bytes)- ignoring'.format(size=read_size))
log.warning('(UDP:{port}) UDP Packet too large ({size} bytes)- ignoring'.format(size=read_size,
port=self.port))
return
data_in, peer_host, peer_port = self.readDatagram(read_size)
data = data_in.decode('utf-8') if isinstance(data_in, bytes) else data_in
log.debug('(UDP) {size} bytes received from {adx} on port {port}'.format(size=len(data),
adx=peer_host.toString(),
port=self.port))
log.debug('(UDP) packet "{data}"'.format(data=data))
log.debug('(UDP) Sending data_received signal to projectors')
log.debug('(UDP:{port}) {size} bytes received from {adx}'.format(size=len(data),
adx=peer_host.toString(),
port=self.port))
log.debug('(UDP:{port}) packet "{data}"'.format(data=data, port=self.port))
log.debug('(UDP:{port}) Sending data_received signal to projectors'.format(port=self.port))
self.data_received.emit(peer_host, self.localPort(), data)
return
@ -144,6 +163,25 @@ class PJLinkUDP(QtNetwork.QUdpSocket):
self.search_active = False
self.search_timer.stop()
def check_settings(self, checked):
"""
Update UDP listening state based on settings change.
NOTE: This method is called by projector settings tab and setup/removed by ProjectorManager
"""
if self.udp_broadcast_listen_setting == checked:
log.debug('(UDP:{port}) No change to status - skipping'.format(port=self.port))
return
self.udp_broadcast_listen_setting = checked
if self.udp_broadcast_listen_setting:
if self.state() == self.ListeningState:
log.debug('(UDP:{port}) Already listening - skipping')
return
self.bind(self.port)
log.debug('(UDP:{port}) Listening'.format(port=self.port))
else:
# Close socket
self.udp_stop()
class PJLinkCommands(object):
"""

View File

@ -27,6 +27,7 @@ import logging
from PyQt5 import QtWidgets
from openlp.core.common.i18n import UiStrings, translate
from openlp.core.common.registry import Registry
from openlp.core.common.settings import Settings
from openlp.core.lib.settingstab import SettingsTab
from openlp.core.projectors import DialogSourceStyle
@ -48,8 +49,11 @@ class ProjectorTab(SettingsTab):
:param parent: Parent widget
"""
self.icon_path = UiIcons().projector
self.udp_listeners = {} # Key on port number
projector_translated = translate('OpenLP.ProjectorTab', 'Projector')
super(ProjectorTab, self).__init__(parent, 'Projector', projector_translated)
Registry().register_function('udp_broadcast_add', self.add_udp_listener)
Registry().register_function('udp_broadcast_remove', self.remove_udp_listener)
def 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.left_layout.addStretch()
self.dialog_type_combo_box.activated.connect(self.on_dialog_type_combo_box_changed)
# Enable/disable listening on UDP ports for PJLink2 broadcasts
self.udp_broadcast_listen = QtWidgets.QCheckBox(self.connect_box)
self.udp_broadcast_listen.setObjectName('udp_broadcast_listen')
self.connect_box_layout.addRow(self.udp_broadcast_listen)
# Connect on LKUP packet received (PJLink v2+ only)
self.connect_on_linkup = QtWidgets.QCheckBox(self.connect_box)
self.connect_on_linkup.setObjectName('connect_on_linkup')
@ -117,6 +125,9 @@ class ProjectorTab(SettingsTab):
translate('OpenLP.ProjectorTab', 'Single dialog box'))
self.connect_on_linkup.setText(
translate('OpenLP.ProjectorTab', 'Connect to projector when LINKUP received (v2 only)'))
self.udp_broadcast_listen.setText(
translate('OpenLP.ProjectorTab', 'Enable listening for PJLink2 broadcast messages'))
log.debug('PJLink settings tab initialized')
def load(self):
"""
@ -126,6 +137,7 @@ class ProjectorTab(SettingsTab):
self.socket_timeout_spin_box.setValue(Settings().value('projector/socket timeout'))
self.socket_poll_spin_box.setValue(Settings().value('projector/poll time'))
self.dialog_type_combo_box.setCurrentIndex(Settings().value('projector/source dialog type'))
self.udp_broadcast_listen.setChecked(Settings().value('projector/udp broadcast listen'))
self.connect_on_linkup.setChecked(Settings().value('projector/connect when LKUP received'))
def save(self):
@ -137,6 +149,41 @@ class ProjectorTab(SettingsTab):
Settings().setValue('projector/poll time', self.socket_poll_spin_box.value())
Settings().setValue('projector/source dialog type', self.dialog_type_combo_box.currentIndex())
Settings().setValue('projector/connect when LKUP received', self.connect_on_linkup.isChecked())
Settings().setValue('projector/udp broadcast listen', self.udp_broadcast_listen.isChecked())
self.call_udp_listener()
def on_dialog_type_combo_box_changed(self):
self.dialog_type = self.dialog_type_combo_box.currentIndex()
def add_udp_listener(self, port, callback):
"""
Add new UDP listener to list
"""
if port in self.udp_listeners:
log.warning('Port {port} already in list - not adding'.format(port=port))
return
self.udp_listeners[port] = callback
log.debug('PJLinkSettings: new callback list: {port}'.format(port=self.udp_listeners.keys()))
def remove_udp_listener(self, port):
"""
Remove UDP listener from list
"""
if port not in self.udp_listeners:
log.warning('Port {port} not in list - ignoring'.format(port=port))
return
# Turn off listener before deleting
self.udp_listeners[port](checked=False)
del self.udp_listeners[port]
log.debug('PJLinkSettings: new callback list: {port}'.format(port=self.udp_listeners.keys()))
def call_udp_listener(self):
"""
Call listeners to update UDP listen setting
"""
if len(self.udp_listeners) < 1:
log.warning('PJLinkSettings: No callers - returning')
return
log.debug('PJLinkSettings: Calling UDP listeners')
for call in self.udp_listeners:
self.udp_listeners[call](checked=self.udp_broadcast_listen.isChecked())

View File

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

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/second bibles': True,
'bibles/primary bible': '',
'bibles/proxy name': '',
'bibles/proxy address': '',
'bibles/proxy username': '',
'bibles/proxy password': '',
'bibles/bible theme': '',
'bibles/verse separator': '',
'bibles/range separator': '',

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.setItem(1, QtWidgets.QFormLayout.LabelRole, self.spacer)
self.select_stack.addWidget(self.open_song_widget)
self.web_tab_widget = QtWidgets.QTabWidget(self.select_page)
self.web_tab_widget.setObjectName('WebTabWidget')
self.web_widget = QtWidgets.QWidget(self.select_page)
self.web_widget.setObjectName('WebWidget')
self.web_bible_tab = QtWidgets.QWidget()
self.web_bible_tab.setObjectName('WebBibleTab')
self.web_bible_layout = QtWidgets.QFormLayout(self.web_bible_tab)
self.web_bible_layout = QtWidgets.QFormLayout(self.web_widget)
self.web_bible_layout.setObjectName('WebBibleLayout')
self.web_update_label = QtWidgets.QLabel(self.web_bible_tab)
self.web_update_label = QtWidgets.QLabel(self.web_widget)
self.web_update_label.setObjectName('WebUpdateLabel')
self.web_bible_layout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.web_update_label)
self.web_update_button = QtWidgets.QPushButton(self.web_bible_tab)
self.web_update_button = QtWidgets.QPushButton(self.web_widget)
self.web_update_button.setObjectName('WebUpdateButton')
self.web_bible_layout.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.web_update_button)
self.web_source_label = QtWidgets.QLabel(self.web_bible_tab)
self.web_source_label = QtWidgets.QLabel(self.web_widget)
self.web_source_label.setObjectName('WebSourceLabel')
self.web_bible_layout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.web_source_label)
self.web_source_combo_box = QtWidgets.QComboBox(self.web_bible_tab)
self.web_source_combo_box = QtWidgets.QComboBox(self.web_widget)
self.web_source_combo_box.setObjectName('WebSourceComboBox')
self.web_source_combo_box.addItems(['', '', ''])
self.web_source_combo_box.setEnabled(False)
@ -245,31 +245,7 @@ class BibleImportForm(OpenLPWizard):
self.web_progress_bar.setObjectName('WebTranslationProgressBar')
self.web_progress_bar.setVisible(False)
self.web_bible_layout.setWidget(3, QtWidgets.QFormLayout.SpanningRole, self.web_progress_bar)
self.web_tab_widget.addTab(self.web_bible_tab, '')
self.web_proxy_tab = QtWidgets.QWidget()
self.web_proxy_tab.setObjectName('WebProxyTab')
self.web_proxy_layout = QtWidgets.QFormLayout(self.web_proxy_tab)
self.web_proxy_layout.setObjectName('WebProxyLayout')
self.web_server_label = QtWidgets.QLabel(self.web_proxy_tab)
self.web_server_label.setObjectName('WebServerLabel')
self.web_proxy_layout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.web_server_label)
self.web_server_edit = QtWidgets.QLineEdit(self.web_proxy_tab)
self.web_server_edit.setObjectName('WebServerEdit')
self.web_proxy_layout.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.web_server_edit)
self.web_user_label = QtWidgets.QLabel(self.web_proxy_tab)
self.web_user_label.setObjectName('WebUserLabel')
self.web_proxy_layout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.web_user_label)
self.web_user_edit = QtWidgets.QLineEdit(self.web_proxy_tab)
self.web_user_edit.setObjectName('WebUserEdit')
self.web_proxy_layout.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.web_user_edit)
self.web_password_label = QtWidgets.QLabel(self.web_proxy_tab)
self.web_password_label.setObjectName('WebPasswordLabel')
self.web_proxy_layout.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.web_password_label)
self.web_password_edit = QtWidgets.QLineEdit(self.web_proxy_tab)
self.web_password_edit.setObjectName('WebPasswordEdit')
self.web_proxy_layout.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.web_password_edit)
self.web_tab_widget.addTab(self.web_proxy_tab, '')
self.select_stack.addWidget(self.web_tab_widget)
self.select_stack.addWidget(self.web_widget)
self.zefania_widget = QtWidgets.QWidget(self.select_page)
self.zefania_widget.setObjectName('ZefaniaWidget')
self.zefania_layout = QtWidgets.QFormLayout(self.zefania_widget)
@ -429,14 +405,6 @@ class BibleImportForm(OpenLPWizard):
self.web_source_combo_box.setItemText(WebDownload.Bibleserver, translate('BiblesPlugin.ImportWizardForm',
'Bibleserver'))
self.web_translation_label.setText(translate('BiblesPlugin.ImportWizardForm', 'Bible:'))
self.web_tab_widget.setTabText(self.web_tab_widget.indexOf(self.web_bible_tab),
translate('BiblesPlugin.ImportWizardForm', 'Download Options'))
self.web_server_label.setText(translate('BiblesPlugin.ImportWizardForm', 'Server:'))
self.web_user_label.setText(translate('BiblesPlugin.ImportWizardForm', 'Username:'))
self.web_password_label.setText(translate('BiblesPlugin.ImportWizardForm', 'Password:'))
self.web_tab_widget.setTabText(
self.web_tab_widget.indexOf(self.web_proxy_tab), translate('BiblesPlugin.ImportWizardForm',
'Proxy Server (Optional)'))
self.sword_bible_label.setText(translate('BiblesPlugin.ImportWizardForm', 'Bibles:'))
self.sword_folder_label.setText(translate('BiblesPlugin.ImportWizardForm', 'SWORD data folder:'))
self.sword_zipfile_label.setText(translate('BiblesPlugin.ImportWizardForm', 'SWORD zip-file:'))
@ -611,11 +579,10 @@ class BibleImportForm(OpenLPWizard):
self.web_update_button.setEnabled(False)
self.web_progress_bar.setVisible(True)
self.web_progress_bar.setValue(0)
proxy_server = self.field('proxy_server')
# TODO: Where does critical_error_message_box get %s string from?
for (download_type, extractor) in ((WebDownload.Crosswalk, CWExtract(proxy_server)),
(WebDownload.BibleGateway, BGExtract(proxy_server)),
(WebDownload.Bibleserver, BSExtract(proxy_server))):
for (download_type, extractor) in ((WebDownload.Crosswalk, CWExtract()),
(WebDownload.BibleGateway, BGExtract()),
(WebDownload.Bibleserver, BSExtract())):
try:
bibles = extractor.get_bibles_from_http()
except (urllib.error.URLError, ConnectionError) as err:
@ -681,9 +648,6 @@ class BibleImportForm(OpenLPWizard):
self.select_page.registerField('web_biblename', self.web_translation_combo_box)
self.select_page.registerField('sword_folder_path', self.sword_folder_path_edit, 'path', PathEdit.pathChanged)
self.select_page.registerField('sword_zip_path', self.sword_zipfile_path_edit, 'path', PathEdit.pathChanged)
self.select_page.registerField('proxy_server', self.web_server_edit)
self.select_page.registerField('proxy_username', self.web_user_edit)
self.select_page.registerField('proxy_password', self.web_password_edit)
self.license_details_page.registerField('license_version', self.version_name_edit)
self.license_details_page.registerField('license_copyright', self.copyright_edit)
self.license_details_page.registerField('license_permissions', self.permissions_edit)
@ -708,9 +672,6 @@ class BibleImportForm(OpenLPWizard):
self.setField('sword_zip_path', None)
self.setField('web_location', WebDownload.Crosswalk)
self.setField('web_biblename', self.web_translation_combo_box.currentIndex())
self.setField('proxy_server', settings.value('proxy address'))
self.setField('proxy_username', settings.value('proxy username'))
self.setField('proxy_password', settings.value('proxy password'))
self.setField('license_version', self.version_name_edit.text())
self.version_name_edit.setPlaceholderText(UiStrings().RequiredShowInFooter)
self.setField('license_copyright', self.copyright_edit.text())
@ -767,9 +728,6 @@ class BibleImportForm(OpenLPWizard):
BibleFormat.WebDownload, name=license_version,
download_source=WebDownload.Names[download_location],
download_name=bible,
proxy_server=self.field('proxy_server'),
proxy_username=self.field('proxy_username'),
proxy_password=self.field('proxy_password'),
language_id=language_id
)
elif bible_type == BibleFormat.Zefania:

View File

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

View File

@ -95,9 +95,8 @@ class BGExtract(RegistryProperties):
"""
NAME = 'BibleGateway'
def __init__(self, proxy_url=None):
log.debug('BGExtract.init(proxy_url="{url}")'.format(url=proxy_url))
self.proxy_url = proxy_url
def __init__(self):
log.debug('BGExtract.__init__()')
socket.setdefaulttimeout(30)
def _remove_elements(self, parent, tag, class_=None):
@ -359,9 +358,8 @@ class BSExtract(RegistryProperties):
"""
NAME = 'BibleServer'
def __init__(self, proxy_url=None):
log.debug('BSExtract.init("{url}")'.format(url=proxy_url))
self.proxy_url = proxy_url
def __init__(self):
log.debug('BSExtract.__init__()')
socket.setdefaulttimeout(30)
def get_bible_chapter(self, version, book_name, chapter):
@ -462,9 +460,8 @@ class CWExtract(RegistryProperties):
"""
NAME = 'Crosswalk'
def __init__(self, proxy_url=None):
log.debug('CWExtract.init("{url}")'.format(url=proxy_url))
self.proxy_url = proxy_url
def __init__(self):
log.debug('CWExtract.__init__()')
socket.setdefaulttimeout(30)
def get_bible_chapter(self, version, book_name, chapter):
@ -596,19 +593,9 @@ class HTTPBible(BibleImport, RegistryProperties):
super().__init__(*args, **kwargs)
self.download_source = kwargs['download_source']
self.download_name = kwargs['download_name']
# TODO: Clean up proxy stuff. We probably want one global proxy per connection type (HTTP and HTTPS) at most.
self.proxy_server = None
self.proxy_username = None
self.proxy_password = None
self.language_id = None
if 'path' in kwargs:
self.path = kwargs['path']
if 'proxy_server' in kwargs:
self.proxy_server = kwargs['proxy_server']
if 'proxy_username' in kwargs:
self.proxy_username = kwargs['proxy_username']
if 'proxy_password' in kwargs:
self.proxy_password = kwargs['proxy_password']
if 'language_id' in kwargs:
self.language_id = kwargs['language_id']
@ -622,20 +609,12 @@ class HTTPBible(BibleImport, RegistryProperties):
'Registering Bible and loading books...'))
self.save_meta('download_source', self.download_source)
self.save_meta('download_name', self.download_name)
if self.proxy_server:
self.save_meta('proxy_server', self.proxy_server)
if self.proxy_username:
# Store the proxy userid.
self.save_meta('proxy_username', self.proxy_username)
if self.proxy_password:
# Store the proxy password.
self.save_meta('proxy_password', self.proxy_password)
if self.download_source.lower() == 'crosswalk':
handler = CWExtract(self.proxy_server)
handler = CWExtract()
elif self.download_source.lower() == 'biblegateway':
handler = BGExtract(self.proxy_server)
handler = BGExtract()
elif self.download_source.lower() == 'bibleserver':
handler = BSExtract(self.proxy_server)
handler = BSExtract()
books = handler.get_books_from_http(self.download_name)
if not books:
log.error('Importing books from {source} - download name: "{name}" '
@ -723,11 +702,11 @@ class HTTPBible(BibleImport, RegistryProperties):
log.debug('HTTPBible.get_chapter("{book}", "{chapter}")'.format(book=book, chapter=chapter))
log.debug('source = {source}'.format(source=self.download_source))
if self.download_source.lower() == 'crosswalk':
handler = CWExtract(self.proxy_server)
handler = CWExtract()
elif self.download_source.lower() == 'biblegateway':
handler = BGExtract(self.proxy_server)
handler = BGExtract()
elif self.download_source.lower() == 'bibleserver':
handler = BSExtract(self.proxy_server)
handler = BSExtract()
return handler.get_bible_chapter(self.download_name, book, chapter)
def get_books(self):

View File

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

View File

@ -24,9 +24,17 @@ The :mod:`upgrade` module provides a way for the database and schema that is the
"""
import logging
from PyQt5 import QtWidgets
from sqlalchemy import Table
from sqlalchemy.sql.expression import delete, select
from openlp.core.common.i18n import translate
from openlp.core.common.settings import ProxyMode, Settings
from openlp.core.lib.db import get_upgrade_op
log = logging.getLogger(__name__)
__version__ = 1
__version__ = 2
# TODO: When removing an upgrade path the ftw-data needs updating to the minimum supported version
@ -37,3 +45,48 @@ def upgrade_1(session, metadata):
This upgrade renamed a number of keys to a single naming convention.
"""
log.info('No upgrades to perform')
def upgrade_2(session, metadata):
"""
Remove the individual proxy settings, after the implementation of central proxy settings.
Added in 2.5 (3.0 development)
"""
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.group_combobox = QtWidgets.QComboBox(choose_group_dialog)
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.new_radio_button = QtWidgets.QRadioButton(choose_group_dialog)
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.new_group_edit = QtWidgets.QLineEdit(choose_group_dialog)
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.group_button_box = create_button_box(choose_group_dialog, 'buttonBox', ['ok'])
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.existing_radio_button.setText(translate('ImagePlugin.ChooseGroupForm', 'Existing 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()):
if self.group_combobox.itemData(index) == selected_group:
self.group_combobox.setCurrentIndex(index)
self.existing_radio_button.setChecked(True)
return QtWidgets.QDialog.exec(self)

View File

@ -430,16 +430,6 @@ class ImageMediaItem(MediaManagerItem):
if isinstance(selected_item.data(0, QtCore.Qt.UserRole), ImageGroups):
preselect_group = selected_item.data(0, QtCore.Qt.UserRole).id
# 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:
self.choose_group_form.existing_radio_button.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'))
if __name__ == '__main__':
def start():
"""
Instantiate and run the application.
"""
@ -60,3 +60,7 @@ if __name__ == '__main__':
if is_macosx():
sys.argv = [x for x in sys.argv if not x.startswith('-psn')]
main()
if __name__ == '__main__':
start()

View File

@ -11,11 +11,8 @@ environment:
install:
# Install dependencies from pypi
- "%PYTHON%\\python.exe -m pip install sqlalchemy alembic chardet beautifulsoup4 Mako nose mock pyodbc==4.0.8 psycopg2 pypiwin32==219 pyenchant websockets asyncio waitress six webob requests QtAwesome"
# Install mysql dependency
- "%PYTHON%\\python.exe -m pip install http://cdn.mysql.com/Downloads/Connector-Python/mysql-connector-python-2.0.4.zip#md5=3df394d89300db95163f17c843ef49df"
# Download and install lxml and pyicu (originally from http://www.lfd.uci.edu/~gohlke/pythonlibs/)
- "%PYTHON%\\python.exe -m pip install https://get.openlp.org/win-sdk/lxml-3.6.4-cp34-cp34m-win32.whl"
- "%PYTHON%\\python.exe -m pip install sqlalchemy alembic appdirs chardet beautifulsoup4 lxml Mako mysql-connector-python nose mock pyodbc==4.0.8 psycopg2 pypiwin32==219 pyenchant pymediainfo websockets asyncio waitress six webob requests QtAwesome"
# Download and install pyicu (originally from http://www.lfd.uci.edu/~gohlke/pythonlibs/)
- "%PYTHON%\\python.exe -m pip install https://get.openlp.org/win-sdk/PyICU-1.9.5-cp34-cp34m-win32.whl"
# Download and install PyQt5
- appveyor DownloadFile http://downloads.sourceforge.net/project/pyqt/PyQt5/PyQt-5.5.1/PyQt5-5.5.1-gpl-Py3.4-Qt5.5.1-x32.exe

View File

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

View File

@ -1,3 +1,4 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
@ -21,9 +22,10 @@
###############################################################################
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'
@ -110,6 +112,34 @@ except Exception:
finally:
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(
name='OpenLP',
@ -157,18 +187,25 @@ using a computer and a data projector.""",
keywords='open source church presentation lyrics projection song bible display project',
author='Raoul Snyman',
author_email='raoulsnyman@openlp.org',
url='http://openlp.org/',
url='https://openlp.org/',
license='GNU General Public License',
packages=find_packages(exclude=['ez_setup', 'examples', 'tests']),
scripts=['openlp.py'],
packages=find_packages(exclude=['ez_setup', 'tests']),
py_modules=['run_openlp'],
include_package_data=True,
zip_safe=False,
install_requires=[
# -*- Extra requirements: -*-
'sqlalchemy',
'alembic'
],
entry_points="""
# -*- Entry points: -*-
"""
python_requires='>=3.6',
install_requires=requires,
extras_require={
'mysql': ['mysql-connector-python'],
'odbc': ['pyodbc'],
'postgresql': ['psycopg2'],
'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:
- Mock
- Nose
- pytest
- 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.
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::
nosetests -v tests
pytest -v tests
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::
nosetests -v tests/functional/test_applocation.py
Finally, to only run a particular test, run the following command::
nosetests -v tests/functional/test_applocation.py:TestAppLocation.get_frozen_path_test
pytest -v tests/functional/openlp_core/test_app.py

View File

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

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 #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2017 OpenLP Developers #
# Copyright (c) 2008-2018 OpenLP Developers #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
@ -20,5 +20,5 @@
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
Module-level functions for the projector test suite
:mod tests/openlp_core/projectors: Tests for projector code
"""

View File

@ -28,15 +28,45 @@ from unittest import TestCase
from unittest.mock import call, patch
import openlp.core.projectors.pjlink
from openlp.core.common.registry import Registry
from openlp.core.projectors.constants import PJLINK_PORT
from openlp.core.projectors.pjlink import PJLinkUDP
from openlp.core.projectors.tab import ProjectorTab
from tests.helpers.testmixin import TestMixin
from tests.resources.projector.data import TEST1_DATA
class TestPJLinkBase(TestCase):
class TestPJLinkBase(TestCase, TestMixin):
"""
Tests for the PJLinkUDP class
"""
def setUp(self):
"""
Create the UI and setup necessary options
"""
self.setup_application()
self.build_settings()
Registry.create()
"""
with patch('openlp.core.projectors.db.init_url') as mocked_init_url:
if os.path.exists(TEST_DB):
os.unlink(TEST_DB)
mocked_init_url.return_value = 'sqlite:///%s' % TEST_DB
self.projectordb = ProjectorDB()
if not hasattr(self, 'projector_manager'):
self.projector_manager = ProjectorManager(projectordb=self.projectordb)
"""
def tearDown(self):
"""
Remove test database.
Delete all the C++ objects at the end so that we don't have a segfault.
"""
# self.projectordb.session.close()
self.destroy_settings()
# del self.projector_manager
@patch.object(openlp.core.projectors.pjlink, 'log')
def test_get_datagram_data_negative_zero_length(self, mock_log):
"""
@ -44,9 +74,9 @@ class TestPJLinkBase(TestCase):
"""
# GIVEN: Test setup
pjlink_udp = PJLinkUDP()
log_warning_calls = [call('(UDP) No data (-1)')]
log_debug_calls = [call('(UDP) PJLinkUDP() Initialized for port 4352'),
call('(UDP) get_datagram() - Receiving data')]
log_warning_calls = [call('(UDP:4352) No data (-1)')]
log_debug_calls = [call('(UDP:4352) PJLinkUDP() Initialized'),
call('(UDP:4352) get_datagram() - Receiving data')]
with patch.object(pjlink_udp, 'pendingDatagramSize') as mock_datagram, \
patch.object(pjlink_udp, 'readDatagram') as mock_read:
mock_datagram.return_value = -1
@ -66,9 +96,9 @@ class TestPJLinkBase(TestCase):
"""
# GIVEN: Test setup
pjlink_udp = PJLinkUDP()
log_warning_calls = [call('(UDP) get_datagram() called when pending data size is 0')]
log_debug_calls = [call('(UDP) PJLinkUDP() Initialized for port 4352'),
call('(UDP) get_datagram() - Receiving data')]
log_warning_calls = [call('(UDP:4352) get_datagram() called when pending data size is 0')]
log_debug_calls = [call('(UDP:4352) PJLinkUDP() Initialized'),
call('(UDP:4352) get_datagram() - Receiving data')]
with patch.object(pjlink_udp, 'pendingDatagramSize') as mock_datagram, \
patch.object(pjlink_udp, 'readDatagram') as mock_read:
mock_datagram.return_value = 0
@ -88,9 +118,9 @@ class TestPJLinkBase(TestCase):
"""
# GIVEN: Test setup
pjlink_udp = PJLinkUDP()
log_warning_calls = [call('(UDP) get_datagram() called when pending data size is 0')]
log_debug_calls = [call('(UDP) PJLinkUDP() Initialized for port 4352'),
call('(UDP) get_datagram() - Receiving data')]
log_warning_calls = [call('(UDP:4352) get_datagram() called when pending data size is 0')]
log_debug_calls = [call('(UDP:4352) PJLinkUDP() Initialized'),
call('(UDP:4352) get_datagram() - Receiving data')]
with patch.object(pjlink_udp, 'pendingDatagramSize') as mock_datagram:
mock_datagram.return_value = 0
@ -100,3 +130,147 @@ class TestPJLinkBase(TestCase):
# THEN: Log entries should be made and method returns
mock_log.warning.assert_has_calls(log_warning_calls)
mock_log.debug.assert_has_calls(log_debug_calls)
@patch.object(openlp.core.projectors.tab, 'log')
def test_pjlinksettings_add_udp_listener(self, mock_log):
"""
Test adding UDP listners to PJLink Settings tab
"""
# GIVEN: Initial setup
log_debug_calls = [call('PJLink settings tab initialized'),
call('PJLinkSettings: new callback list: dict_keys([4352])')]
log_warning_calls = []
pjlink_udp = PJLinkUDP()
settings_tab = ProjectorTab(parent=None)
# WHEN: add_udp_listener is called with single port
settings_tab.add_udp_listener(port=pjlink_udp.port, callback=pjlink_udp.check_settings)
# THEN: settings tab should have one entry
assert len(settings_tab.udp_listeners) == 1
assert pjlink_udp.port in settings_tab.udp_listeners
mock_log.debug.assert_has_calls(log_debug_calls)
mock_log.warning.assert_has_calls(log_warning_calls)
@patch.object(openlp.core.projectors.tab, 'log')
def test_pjlinksettings_add_udp_listener_multiple_same(self, mock_log):
"""
Test adding second UDP listner with same port to PJLink Settings tab
"""
# GIVEN: Initial setup
log_debug_calls = [call('PJLink settings tab initialized'),
call('PJLinkSettings: new callback list: dict_keys([4352])')]
log_warning_calls = [call('Port 4352 already in list - not adding')]
pjlink_udp = PJLinkUDP()
settings_tab = ProjectorTab(parent=None)
settings_tab.add_udp_listener(port=pjlink_udp.port, callback=pjlink_udp.check_settings)
# WHEN: add_udp_listener is called with second instance same port
settings_tab.add_udp_listener(port=pjlink_udp.port, callback=pjlink_udp.check_settings)
# THEN: settings tab should have one entry
assert len(settings_tab.udp_listeners) == 1
assert pjlink_udp.port in settings_tab.udp_listeners
mock_log.debug.assert_has_calls(log_debug_calls)
mock_log.warning.assert_has_calls(log_warning_calls)
@patch.object(openlp.core.projectors.tab, 'log')
def test_pjlinksettings_add_udp_listener_multiple_different(self, mock_log):
"""
Test adding second UDP listner with different port to PJLink Settings tab
"""
# GIVEN: Initial setup
log_debug_calls = [call('PJLink settings tab initialized'),
call('PJLinkSettings: new callback list: dict_keys([4352])')]
log_warning_calls = []
settings_tab = ProjectorTab(parent=None)
pjlink_udp1 = PJLinkUDP(port=4352)
settings_tab.add_udp_listener(port=pjlink_udp1.port, callback=pjlink_udp1.check_settings)
# WHEN: add_udp_listener is called with second instance different port
pjlink_udp2 = PJLinkUDP(port=4353)
settings_tab.add_udp_listener(port=pjlink_udp2.port, callback=pjlink_udp2.check_settings)
# THEN: settings tab should have two entry
assert len(settings_tab.udp_listeners) == 2
assert pjlink_udp1.port in settings_tab.udp_listeners
assert pjlink_udp2.port in settings_tab.udp_listeners
mock_log.debug.assert_has_calls(log_debug_calls)
mock_log.warning.assert_has_calls(log_warning_calls)
@patch.object(openlp.core.projectors.tab, 'log')
def test_pjlinksettings_remove_udp_listener(self, mock_log):
"""
Test removing UDP listners to PJLink Settings tab
"""
# GIVEN: Initial setup
log_debug_calls = [call('PJLink settings tab initialized'),
call('PJLinkSettings: new callback list: dict_keys([4352])'),
call('PJLinkSettings: new callback list: dict_keys([])')]
log_warning_calls = []
pjlink_udp = PJLinkUDP()
settings_tab = ProjectorTab(parent=None)
settings_tab.add_udp_listener(port=pjlink_udp.port, callback=pjlink_udp.check_settings)
# WHEN: remove_udp_listener is called with single port
settings_tab.remove_udp_listener(port=pjlink_udp.port)
# THEN: settings tab should have one entry
assert len(settings_tab.udp_listeners) == 0
mock_log.debug.assert_has_calls(log_debug_calls)
mock_log.warning.assert_has_calls(log_warning_calls)
@patch.object(openlp.core.projectors.tab, 'log')
def test_pjlinksettings_remove_udp_listener_multiple_different(self, mock_log):
"""
Test adding second UDP listner with different port to PJLink Settings tab
"""
# GIVEN: Initial setup
log_debug_calls = [call('PJLink settings tab initialized'),
call('PJLinkSettings: new callback list: dict_keys([4352])')]
log_warning_calls = []
settings_tab = ProjectorTab(parent=None)
pjlink_udp1 = PJLinkUDP(port=4352)
settings_tab.add_udp_listener(port=pjlink_udp1.port, callback=pjlink_udp1.check_settings)
pjlink_udp2 = PJLinkUDP(port=4353)
settings_tab.add_udp_listener(port=pjlink_udp2.port, callback=pjlink_udp2.check_settings)
# WHEN: remove_udp_listener called for one port
settings_tab.remove_udp_listener(port=4353)
# THEN: settings tab should have one entry
assert len(settings_tab.udp_listeners) == 1
assert pjlink_udp1.port in settings_tab.udp_listeners
assert pjlink_udp2.port not in settings_tab.udp_listeners
mock_log.debug.assert_has_calls(log_debug_calls)
mock_log.warning.assert_has_calls(log_warning_calls)
@patch.object(PJLinkUDP, 'check_settings')
@patch.object(openlp.core.projectors.pjlink, 'log')
@patch.object(openlp.core.projectors.tab, 'log')
def test_pjlinksettings_call_udp_listener(self, mock_tab_log, mock_pjlink_log, mock_check_settings):
"""
Test calling UDP listners in PJLink Settings tab
"""
# GIVEN: Initial setup
tab_debug_calls = [call('PJLink settings tab initialized'),
call('PJLinkSettings: new callback list: dict_keys([4352])'),
call('PJLinkSettings: Calling UDP listeners')]
pjlink_debug_calls = [call.debug('(UDP:4352) PJLinkUDP() Initialized')]
pjlink_udp = PJLinkUDP()
settings_tab = ProjectorTab(parent=None)
settings_tab.add_udp_listener(port=pjlink_udp.port, callback=pjlink_udp.check_settings)
# WHEN: calling UDP listener via registry
settings_tab.call_udp_listener()
# THEN: settings tab should have one entry
assert len(settings_tab.udp_listeners) == 1
mock_check_settings.assert_called()
mock_tab_log.debug.assert_has_calls(tab_debug_calls)
mock_pjlink_log.assert_has_calls(pjlink_debug_calls)

Binary file not shown.

Binary file not shown.