This commit is contained in:
Tim Bentley 2016-08-12 19:21:30 +01:00
commit 225160be24
33 changed files with 1498 additions and 1546 deletions

View File

@ -1,17 +1,22 @@
[unittest]
verbose = True
verbose = true
plugins = nose2.plugins.mp
[log-capture]
always-on = True
clear-handlers = True
always-on = true
clear-handlers = true
filter = -nose
log-level = ERROR
[test-result]
always-on = True
descriptions = True
always-on = true
descriptions = true
[coverage]
always-on = False
always-on = true
coverage = openlp
coverage-report = html
[multiprocess]
always-on = false
processes = 4

View File

@ -30,6 +30,7 @@ import os
import re
import sys
import traceback
from chardet.universaldetector import UniversalDetector
from ipaddress import IPv4Address, IPv6Address, AddressValueError
from shutil import which
from subprocess import check_output, CalledProcessError, STDOUT
@ -416,3 +417,24 @@ def check_binary_exists(program_path):
runlog = ''
log.debug('check_output returned: {text}'.format(text=runlog))
return runlog
def get_file_encoding(filename):
"""
Utility function to incrementally detect the file encoding.
:param filename: Filename for the file to determine the encoding for. Str
:return: A dict with the keys 'encoding' and 'confidence'
"""
detector = UniversalDetector()
try:
with open(filename, 'rb') as detect_file:
while not detector.done:
chunk = detect_file.read(1024)
if not chunk:
break
detector.feed(chunk)
detector.close()
return detector.result
except OSError:
log.exception('Error detecting file encoding')

View File

@ -24,9 +24,15 @@ The :mod:`~openlp.core.lib.exceptions` module contains custom exceptions
"""
# TODO: Test __init__ & __str__
class ValidationError(Exception):
"""
The :class:`~openlp.core.lib.exceptions.ValidationError` exception provides a custom exception for validating
import files.
"""
pass
def __init__(self, msg="Validation Error"):
self.msg = msg
def __str__(self):
return '{error_message}'.format(error_message=self.msg)

View File

@ -131,227 +131,243 @@ S_INFO = 310
S_NETWORK_SENDING = 400
S_NETWORK_RECEIVED = 401
CONNECTION_ERRORS = {E_NOT_CONNECTED, E_NO_AUTHENTICATION, E_AUTHENTICATION, E_CLASS,
E_PREFIX, E_CONNECTION_REFUSED, E_REMOTE_HOST_CLOSED_CONNECTION,
E_HOST_NOT_FOUND, E_SOCKET_ACCESS, E_SOCKET_RESOURCE, E_SOCKET_TIMEOUT,
E_DATAGRAM_TOO_LARGE, E_NETWORK, E_ADDRESS_IN_USE, E_SOCKET_ADDRESS_NOT_AVAILABLE,
E_UNSUPPORTED_SOCKET_OPERATION, E_PROXY_AUTHENTICATION_REQUIRED,
E_SLS_HANDSHAKE_FAILED, E_UNFINISHED_SOCKET_OPERATION, E_PROXY_CONNECTION_REFUSED,
E_PROXY_CONNECTION_CLOSED, E_PROXY_CONNECTION_TIMEOUT, E_PROXY_NOT_FOUND,
E_PROXY_PROTOCOL, E_UNKNOWN_SOCKET_ERROR
}
CONNECTION_ERRORS = {
E_NOT_CONNECTED, E_NO_AUTHENTICATION, E_AUTHENTICATION, E_CLASS,
E_PREFIX, E_CONNECTION_REFUSED, E_REMOTE_HOST_CLOSED_CONNECTION,
E_HOST_NOT_FOUND, E_SOCKET_ACCESS, E_SOCKET_RESOURCE, E_SOCKET_TIMEOUT,
E_DATAGRAM_TOO_LARGE, E_NETWORK, E_ADDRESS_IN_USE, E_SOCKET_ADDRESS_NOT_AVAILABLE,
E_UNSUPPORTED_SOCKET_OPERATION, E_PROXY_AUTHENTICATION_REQUIRED,
E_SLS_HANDSHAKE_FAILED, E_UNFINISHED_SOCKET_OPERATION, E_PROXY_CONNECTION_REFUSED,
E_PROXY_CONNECTION_CLOSED, E_PROXY_CONNECTION_TIMEOUT, E_PROXY_NOT_FOUND,
E_PROXY_PROTOCOL, E_UNKNOWN_SOCKET_ERROR
}
PJLINK_ERRORS = {'ERRA': E_AUTHENTICATION, # Authentication error
'ERR1': E_UNDEFINED, # Undefined command error
'ERR2': E_PARAMETER, # Invalid parameter error
'ERR3': E_UNAVAILABLE, # Projector busy
'ERR4': E_PROJECTOR, # Projector or display failure
E_AUTHENTICATION: 'ERRA',
E_UNDEFINED: 'ERR1',
E_PARAMETER: 'ERR2',
E_UNAVAILABLE: 'ERR3',
E_PROJECTOR: 'ERR4'}
PJLINK_ERRORS = {
'ERRA': E_AUTHENTICATION, # Authentication error
'ERR1': E_UNDEFINED, # Undefined command error
'ERR2': E_PARAMETER, # Invalid parameter error
'ERR3': E_UNAVAILABLE, # Projector busy
'ERR4': E_PROJECTOR, # Projector or display failure
E_AUTHENTICATION: 'ERRA',
E_UNDEFINED: 'ERR1',
E_PARAMETER: 'ERR2',
E_UNAVAILABLE: 'ERR3',
E_PROJECTOR: 'ERR4'
}
# Map error/status codes to string
ERROR_STRING = {0: 'S_OK',
E_GENERAL: 'E_GENERAL',
E_NOT_CONNECTED: 'E_NOT_CONNECTED',
E_FAN: 'E_FAN',
E_LAMP: 'E_LAMP',
E_TEMP: 'E_TEMP',
E_COVER: 'E_COVER',
E_FILTER: 'E_FILTER',
E_AUTHENTICATION: 'E_AUTHENTICATION',
E_NO_AUTHENTICATION: 'E_NO_AUTHENTICATION',
E_UNDEFINED: 'E_UNDEFINED',
E_PARAMETER: 'E_PARAMETER',
E_UNAVAILABLE: 'E_UNAVAILABLE',
E_PROJECTOR: 'E_PROJECTOR',
E_INVALID_DATA: 'E_INVALID_DATA',
E_WARN: 'E_WARN',
E_ERROR: 'E_ERROR',
E_CLASS: 'E_CLASS',
E_PREFIX: 'E_PREFIX', # Last projector error
E_CONNECTION_REFUSED: 'E_CONNECTION_REFUSED', # First QtSocket error
E_REMOTE_HOST_CLOSED_CONNECTION: 'E_REMOTE_HOST_CLOSED_CONNECTION',
E_HOST_NOT_FOUND: 'E_HOST_NOT_FOUND',
E_SOCKET_ACCESS: 'E_SOCKET_ACCESS',
E_SOCKET_RESOURCE: 'E_SOCKET_RESOURCE',
E_SOCKET_TIMEOUT: 'E_SOCKET_TIMEOUT',
E_DATAGRAM_TOO_LARGE: 'E_DATAGRAM_TOO_LARGE',
E_NETWORK: 'E_NETWORK',
E_ADDRESS_IN_USE: 'E_ADDRESS_IN_USE',
E_SOCKET_ADDRESS_NOT_AVAILABLE: 'E_SOCKET_ADDRESS_NOT_AVAILABLE',
E_UNSUPPORTED_SOCKET_OPERATION: 'E_UNSUPPORTED_SOCKET_OPERATION',
E_PROXY_AUTHENTICATION_REQUIRED: 'E_PROXY_AUTHENTICATION_REQUIRED',
E_SLS_HANDSHAKE_FAILED: 'E_SLS_HANDSHAKE_FAILED',
E_UNFINISHED_SOCKET_OPERATION: 'E_UNFINISHED_SOCKET_OPERATION',
E_PROXY_CONNECTION_REFUSED: 'E_PROXY_CONNECTION_REFUSED',
E_PROXY_CONNECTION_CLOSED: 'E_PROXY_CONNECTION_CLOSED',
E_PROXY_CONNECTION_TIMEOUT: 'E_PROXY_CONNECTION_TIMEOUT',
E_PROXY_NOT_FOUND: 'E_PROXY_NOT_FOUND',
E_PROXY_PROTOCOL: 'E_PROXY_PROTOCOL',
E_UNKNOWN_SOCKET_ERROR: 'E_UNKNOWN_SOCKET_ERROR'}
ERROR_STRING = {
0: 'S_OK',
E_GENERAL: 'E_GENERAL',
E_NOT_CONNECTED: 'E_NOT_CONNECTED',
E_FAN: 'E_FAN',
E_LAMP: 'E_LAMP',
E_TEMP: 'E_TEMP',
E_COVER: 'E_COVER',
E_FILTER: 'E_FILTER',
E_AUTHENTICATION: 'E_AUTHENTICATION',
E_NO_AUTHENTICATION: 'E_NO_AUTHENTICATION',
E_UNDEFINED: 'E_UNDEFINED',
E_PARAMETER: 'E_PARAMETER',
E_UNAVAILABLE: 'E_UNAVAILABLE',
E_PROJECTOR: 'E_PROJECTOR',
E_INVALID_DATA: 'E_INVALID_DATA',
E_WARN: 'E_WARN',
E_ERROR: 'E_ERROR',
E_CLASS: 'E_CLASS',
E_PREFIX: 'E_PREFIX', # Last projector error
E_CONNECTION_REFUSED: 'E_CONNECTION_REFUSED', # First QtSocket error
E_REMOTE_HOST_CLOSED_CONNECTION: 'E_REMOTE_HOST_CLOSED_CONNECTION',
E_HOST_NOT_FOUND: 'E_HOST_NOT_FOUND',
E_SOCKET_ACCESS: 'E_SOCKET_ACCESS',
E_SOCKET_RESOURCE: 'E_SOCKET_RESOURCE',
E_SOCKET_TIMEOUT: 'E_SOCKET_TIMEOUT',
E_DATAGRAM_TOO_LARGE: 'E_DATAGRAM_TOO_LARGE',
E_NETWORK: 'E_NETWORK',
E_ADDRESS_IN_USE: 'E_ADDRESS_IN_USE',
E_SOCKET_ADDRESS_NOT_AVAILABLE: 'E_SOCKET_ADDRESS_NOT_AVAILABLE',
E_UNSUPPORTED_SOCKET_OPERATION: 'E_UNSUPPORTED_SOCKET_OPERATION',
E_PROXY_AUTHENTICATION_REQUIRED: 'E_PROXY_AUTHENTICATION_REQUIRED',
E_SLS_HANDSHAKE_FAILED: 'E_SLS_HANDSHAKE_FAILED',
E_UNFINISHED_SOCKET_OPERATION: 'E_UNFINISHED_SOCKET_OPERATION',
E_PROXY_CONNECTION_REFUSED: 'E_PROXY_CONNECTION_REFUSED',
E_PROXY_CONNECTION_CLOSED: 'E_PROXY_CONNECTION_CLOSED',
E_PROXY_CONNECTION_TIMEOUT: 'E_PROXY_CONNECTION_TIMEOUT',
E_PROXY_NOT_FOUND: 'E_PROXY_NOT_FOUND',
E_PROXY_PROTOCOL: 'E_PROXY_PROTOCOL',
E_UNKNOWN_SOCKET_ERROR: 'E_UNKNOWN_SOCKET_ERROR'
}
STATUS_STRING = {S_NOT_CONNECTED: 'S_NOT_CONNECTED',
S_CONNECTING: 'S_CONNECTING',
S_CONNECTED: 'S_CONNECTED',
S_STATUS: 'S_STATUS',
S_OFF: 'S_OFF',
S_INITIALIZE: 'S_INITIALIZE',
S_STANDBY: 'S_STANDBY',
S_WARMUP: 'S_WARMUP',
S_ON: 'S_ON',
S_COOLDOWN: 'S_COOLDOWN',
S_INFO: 'S_INFO',
S_NETWORK_SENDING: 'S_NETWORK_SENDING',
S_NETWORK_RECEIVED: 'S_NETWORK_RECEIVED'}
STATUS_STRING = {
S_NOT_CONNECTED: 'S_NOT_CONNECTED',
S_CONNECTING: 'S_CONNECTING',
S_CONNECTED: 'S_CONNECTED',
S_STATUS: 'S_STATUS',
S_OFF: 'S_OFF',
S_INITIALIZE: 'S_INITIALIZE',
S_STANDBY: 'S_STANDBY',
S_WARMUP: 'S_WARMUP',
S_ON: 'S_ON',
S_COOLDOWN: 'S_COOLDOWN',
S_INFO: 'S_INFO',
S_NETWORK_SENDING: 'S_NETWORK_SENDING',
S_NETWORK_RECEIVED: 'S_NETWORK_RECEIVED'
}
# Map error/status codes to message strings
ERROR_MSG = {E_OK: translate('OpenLP.ProjectorConstants', 'OK'), # E_OK | S_OK
E_GENERAL: translate('OpenLP.ProjectorConstants', 'General projector error'),
E_NOT_CONNECTED: translate('OpenLP.ProjectorConstants', 'Not connected error'),
E_LAMP: translate('OpenLP.ProjectorConstants', 'Lamp error'),
E_FAN: translate('OpenLP.ProjectorConstants', 'Fan error'),
E_TEMP: translate('OpenLP.ProjectorConstants', 'High temperature detected'),
E_COVER: translate('OpenLP.ProjectorConstants', 'Cover open detected'),
E_FILTER: translate('OpenLP.ProjectorConstants', 'Check filter'),
E_AUTHENTICATION: translate('OpenLP.ProjectorConstants', 'Authentication Error'),
E_UNDEFINED: translate('OpenLP.ProjectorConstants', 'Undefined Command'),
E_PARAMETER: translate('OpenLP.ProjectorConstants', 'Invalid Parameter'),
E_UNAVAILABLE: translate('OpenLP.ProjectorConstants', 'Projector Busy'),
E_PROJECTOR: translate('OpenLP.ProjectorConstants', 'Projector/Display Error'),
E_INVALID_DATA: translate('OpenLP.ProjectorConstants', 'Invalid packet received'),
E_WARN: translate('OpenLP.ProjectorConstants', 'Warning condition detected'),
E_ERROR: translate('OpenLP.ProjectorConstants', 'Error condition detected'),
E_CLASS: translate('OpenLP.ProjectorConstants', 'PJLink class not supported'),
E_PREFIX: translate('OpenLP.ProjectorConstants', 'Invalid prefix character'),
E_CONNECTION_REFUSED: translate('OpenLP.ProjectorConstants',
'The connection was refused by the peer (or timed out)'),
E_REMOTE_HOST_CLOSED_CONNECTION: translate('OpenLP.ProjectorConstants',
'The remote host closed the connection'),
E_HOST_NOT_FOUND: translate('OpenLP.ProjectorConstants', 'The host address was not found'),
E_SOCKET_ACCESS: translate('OpenLP.ProjectorConstants',
'The socket operation failed because the application '
'lacked the required privileges'),
E_SOCKET_RESOURCE: translate('OpenLP.ProjectorConstants',
'The local system ran out of resources (e.g., too many sockets)'),
E_SOCKET_TIMEOUT: translate('OpenLP.ProjectorConstants',
'The socket operation timed out'),
E_DATAGRAM_TOO_LARGE: translate('OpenLP.ProjectorConstants',
'The datagram was larger than the operating system\'s limit'),
E_NETWORK: translate('OpenLP.ProjectorConstants',
'An error occurred with the network (Possibly someone pulled the plug?)'),
E_ADDRESS_IN_USE: translate('OpenLP.ProjectorConstants',
'The address specified with socket.bind() '
'is already in use and was set to be exclusive'),
E_SOCKET_ADDRESS_NOT_AVAILABLE: translate('OpenLP.ProjectorConstants',
'The address specified to socket.bind() '
'does not belong to the host'),
E_UNSUPPORTED_SOCKET_OPERATION: translate('OpenLP.ProjectorConstants',
'The requested socket operation is not supported by the local '
'operating system (e.g., lack of IPv6 support)'),
E_PROXY_AUTHENTICATION_REQUIRED: translate('OpenLP.ProjectorConstants',
'The socket is using a proxy, '
'and the proxy requires authentication'),
E_SLS_HANDSHAKE_FAILED: translate('OpenLP.ProjectorConstants',
'The SSL/TLS handshake failed'),
E_UNFINISHED_SOCKET_OPERATION: translate('OpenLP.ProjectorConstants',
'The last operation attempted has not finished yet '
'(still in progress in the background)'),
E_PROXY_CONNECTION_REFUSED: translate('OpenLP.ProjectorConstants',
'Could not contact the proxy server because the connection '
'to that server was denied'),
E_PROXY_CONNECTION_CLOSED: translate('OpenLP.ProjectorConstants',
'The connection to the proxy server was closed unexpectedly '
'(before the connection to the final peer was established)'),
E_PROXY_CONNECTION_TIMEOUT: translate('OpenLP.ProjectorConstants',
'The connection to the proxy server timed out or the proxy '
'server stopped responding in the authentication phase.'),
E_PROXY_NOT_FOUND: translate('OpenLP.ProjectorConstants',
'The proxy address set with setProxy() was not found'),
E_PROXY_PROTOCOL: translate('OpenLP.ProjectorConstants',
'The connection negotiation with the proxy server failed because the '
'response from the proxy server could not be understood'),
E_UNKNOWN_SOCKET_ERROR: translate('OpenLP.ProjectorConstants', 'An unidentified error occurred'),
S_NOT_CONNECTED: translate('OpenLP.ProjectorConstants', 'Not connected'),
S_CONNECTING: translate('OpenLP.ProjectorConstants', 'Connecting'),
S_CONNECTED: translate('OpenLP.ProjectorConstants', 'Connected'),
S_STATUS: translate('OpenLP.ProjectorConstants', 'Getting status'),
S_OFF: translate('OpenLP.ProjectorConstants', 'Off'),
S_INITIALIZE: translate('OpenLP.ProjectorConstants', 'Initialize in progress'),
S_STANDBY: translate('OpenLP.ProjectorConstants', 'Power in standby'),
S_WARMUP: translate('OpenLP.ProjectorConstants', 'Warmup in progress'),
S_ON: translate('OpenLP.ProjectorConstants', 'Power is on'),
S_COOLDOWN: translate('OpenLP.ProjectorConstants', 'Cooldown in progress'),
S_INFO: translate('OpenLP.ProjectorConstants', 'Projector Information available'),
S_NETWORK_SENDING: translate('OpenLP.ProjectorConstants', 'Sending data'),
S_NETWORK_RECEIVED: translate('OpenLP.ProjectorConstants', 'Received data')}
ERROR_MSG = {
E_OK: translate('OpenLP.ProjectorConstants', 'OK'), # E_OK | S_OK
E_GENERAL: translate('OpenLP.ProjectorConstants', 'General projector error'),
E_NOT_CONNECTED: translate('OpenLP.ProjectorConstants', 'Not connected error'),
E_LAMP: translate('OpenLP.ProjectorConstants', 'Lamp error'),
E_FAN: translate('OpenLP.ProjectorConstants', 'Fan error'),
E_TEMP: translate('OpenLP.ProjectorConstants', 'High temperature detected'),
E_COVER: translate('OpenLP.ProjectorConstants', 'Cover open detected'),
E_FILTER: translate('OpenLP.ProjectorConstants', 'Check filter'),
E_AUTHENTICATION: translate('OpenLP.ProjectorConstants', 'Authentication Error'),
E_UNDEFINED: translate('OpenLP.ProjectorConstants', 'Undefined Command'),
E_PARAMETER: translate('OpenLP.ProjectorConstants', 'Invalid Parameter'),
E_UNAVAILABLE: translate('OpenLP.ProjectorConstants', 'Projector Busy'),
E_PROJECTOR: translate('OpenLP.ProjectorConstants', 'Projector/Display Error'),
E_INVALID_DATA: translate('OpenLP.ProjectorConstants', 'Invalid packet received'),
E_WARN: translate('OpenLP.ProjectorConstants', 'Warning condition detected'),
E_ERROR: translate('OpenLP.ProjectorConstants', 'Error condition detected'),
E_CLASS: translate('OpenLP.ProjectorConstants', 'PJLink class not supported'),
E_PREFIX: translate('OpenLP.ProjectorConstants', 'Invalid prefix character'),
E_CONNECTION_REFUSED: translate('OpenLP.ProjectorConstants',
'The connection was refused by the peer (or timed out)'),
E_REMOTE_HOST_CLOSED_CONNECTION: translate('OpenLP.ProjectorConstants',
'The remote host closed the connection'),
E_HOST_NOT_FOUND: translate('OpenLP.ProjectorConstants', 'The host address was not found'),
E_SOCKET_ACCESS: translate('OpenLP.ProjectorConstants',
'The socket operation failed because the application '
'lacked the required privileges'),
E_SOCKET_RESOURCE: translate('OpenLP.ProjectorConstants',
'The local system ran out of resources (e.g., too many sockets)'),
E_SOCKET_TIMEOUT: translate('OpenLP.ProjectorConstants',
'The socket operation timed out'),
E_DATAGRAM_TOO_LARGE: translate('OpenLP.ProjectorConstants',
'The datagram was larger than the operating system\'s limit'),
E_NETWORK: translate('OpenLP.ProjectorConstants',
'An error occurred with the network (Possibly someone pulled the plug?)'),
E_ADDRESS_IN_USE: translate('OpenLP.ProjectorConstants',
'The address specified with socket.bind() '
'is already in use and was set to be exclusive'),
E_SOCKET_ADDRESS_NOT_AVAILABLE: translate('OpenLP.ProjectorConstants',
'The address specified to socket.bind() '
'does not belong to the host'),
E_UNSUPPORTED_SOCKET_OPERATION: translate('OpenLP.ProjectorConstants',
'The requested socket operation is not supported by the local '
'operating system (e.g., lack of IPv6 support)'),
E_PROXY_AUTHENTICATION_REQUIRED: translate('OpenLP.ProjectorConstants',
'The socket is using a proxy, '
'and the proxy requires authentication'),
E_SLS_HANDSHAKE_FAILED: translate('OpenLP.ProjectorConstants',
'The SSL/TLS handshake failed'),
E_UNFINISHED_SOCKET_OPERATION: translate('OpenLP.ProjectorConstants',
'The last operation attempted has not finished yet '
'(still in progress in the background)'),
E_PROXY_CONNECTION_REFUSED: translate('OpenLP.ProjectorConstants',
'Could not contact the proxy server because the connection '
'to that server was denied'),
E_PROXY_CONNECTION_CLOSED: translate('OpenLP.ProjectorConstants',
'The connection to the proxy server was closed unexpectedly '
'(before the connection to the final peer was established)'),
E_PROXY_CONNECTION_TIMEOUT: translate('OpenLP.ProjectorConstants',
'The connection to the proxy server timed out or the proxy '
'server stopped responding in the authentication phase.'),
E_PROXY_NOT_FOUND: translate('OpenLP.ProjectorConstants',
'The proxy address set with setProxy() was not found'),
E_PROXY_PROTOCOL: translate('OpenLP.ProjectorConstants',
'The connection negotiation with the proxy server failed because the '
'response from the proxy server could not be understood'),
E_UNKNOWN_SOCKET_ERROR: translate('OpenLP.ProjectorConstants', 'An unidentified error occurred'),
S_NOT_CONNECTED: translate('OpenLP.ProjectorConstants', 'Not connected'),
S_CONNECTING: translate('OpenLP.ProjectorConstants', 'Connecting'),
S_CONNECTED: translate('OpenLP.ProjectorConstants', 'Connected'),
S_STATUS: translate('OpenLP.ProjectorConstants', 'Getting status'),
S_OFF: translate('OpenLP.ProjectorConstants', 'Off'),
S_INITIALIZE: translate('OpenLP.ProjectorConstants', 'Initialize in progress'),
S_STANDBY: translate('OpenLP.ProjectorConstants', 'Power in standby'),
S_WARMUP: translate('OpenLP.ProjectorConstants', 'Warmup in progress'),
S_ON: translate('OpenLP.ProjectorConstants', 'Power is on'),
S_COOLDOWN: translate('OpenLP.ProjectorConstants', 'Cooldown in progress'),
S_INFO: translate('OpenLP.ProjectorConstants', 'Projector Information available'),
S_NETWORK_SENDING: translate('OpenLP.ProjectorConstants', 'Sending data'),
S_NETWORK_RECEIVED: translate('OpenLP.ProjectorConstants', 'Received data')
}
# Map for ERST return codes to string
PJLINK_ERST_STATUS = {'0': ERROR_STRING[E_OK],
'1': ERROR_STRING[E_WARN],
'2': ERROR_STRING[E_ERROR]}
PJLINK_ERST_STATUS = {
'0': ERROR_STRING[E_OK],
'1': ERROR_STRING[E_WARN],
'2': ERROR_STRING[E_ERROR]
}
# Map for POWR return codes to status code
PJLINK_POWR_STATUS = {'0': S_STANDBY,
'1': S_ON,
'2': S_COOLDOWN,
'3': S_WARMUP,
S_STANDBY: '0',
S_ON: '1',
S_COOLDOWN: '2',
S_WARMUP: '3'}
PJLINK_POWR_STATUS = {
'0': S_STANDBY,
'1': S_ON,
'2': S_COOLDOWN,
'3': S_WARMUP,
S_STANDBY: '0',
S_ON: '1',
S_COOLDOWN: '2',
S_WARMUP: '3'
}
PJLINK_DEFAULT_SOURCES = {'1': translate('OpenLP.DB', 'RGB'),
'2': translate('OpenLP.DB', 'Video'),
'3': translate('OpenLP.DB', 'Digital'),
'4': translate('OpenLP.DB', 'Storage'),
'5': translate('OpenLP.DB', 'Network')}
PJLINK_DEFAULT_SOURCES = {
'1': translate('OpenLP.DB', 'RGB'),
'2': translate('OpenLP.DB', 'Video'),
'3': translate('OpenLP.DB', 'Digital'),
'4': translate('OpenLP.DB', 'Storage'),
'5': translate('OpenLP.DB', 'Network')
}
PJLINK_DEFAULT_CODES = {'11': translate('OpenLP.DB', 'RGB 1'),
'12': translate('OpenLP.DB', 'RGB 2'),
'13': translate('OpenLP.DB', 'RGB 3'),
'14': translate('OpenLP.DB', 'RGB 4'),
'15': translate('OpenLP.DB', 'RGB 5'),
'16': translate('OpenLP.DB', 'RGB 6'),
'17': translate('OpenLP.DB', 'RGB 7'),
'18': translate('OpenLP.DB', 'RGB 8'),
'19': translate('OpenLP.DB', 'RGB 9'),
'21': translate('OpenLP.DB', 'Video 1'),
'22': translate('OpenLP.DB', 'Video 2'),
'23': translate('OpenLP.DB', 'Video 3'),
'24': translate('OpenLP.DB', 'Video 4'),
'25': translate('OpenLP.DB', 'Video 5'),
'26': translate('OpenLP.DB', 'Video 6'),
'27': translate('OpenLP.DB', 'Video 7'),
'28': translate('OpenLP.DB', 'Video 8'),
'29': translate('OpenLP.DB', 'Video 9'),
'31': translate('OpenLP.DB', 'Digital 1'),
'32': translate('OpenLP.DB', 'Digital 2'),
'33': translate('OpenLP.DB', 'Digital 3'),
'34': translate('OpenLP.DB', 'Digital 4'),
'35': translate('OpenLP.DB', 'Digital 5'),
'36': translate('OpenLP.DB', 'Digital 6'),
'37': translate('OpenLP.DB', 'Digital 7'),
'38': translate('OpenLP.DB', 'Digital 8'),
'39': translate('OpenLP.DB', 'Digital 9'),
'41': translate('OpenLP.DB', 'Storage 1'),
'42': translate('OpenLP.DB', 'Storage 2'),
'43': translate('OpenLP.DB', 'Storage 3'),
'44': translate('OpenLP.DB', 'Storage 4'),
'45': translate('OpenLP.DB', 'Storage 5'),
'46': translate('OpenLP.DB', 'Storage 6'),
'47': translate('OpenLP.DB', 'Storage 7'),
'48': translate('OpenLP.DB', 'Storage 8'),
'49': translate('OpenLP.DB', 'Storage 9'),
'51': translate('OpenLP.DB', 'Network 1'),
'52': translate('OpenLP.DB', 'Network 2'),
'53': translate('OpenLP.DB', 'Network 3'),
'54': translate('OpenLP.DB', 'Network 4'),
'55': translate('OpenLP.DB', 'Network 5'),
'56': translate('OpenLP.DB', 'Network 6'),
'57': translate('OpenLP.DB', 'Network 7'),
'58': translate('OpenLP.DB', 'Network 8'),
'59': translate('OpenLP.DB', 'Network 9')
}
PJLINK_DEFAULT_CODES = {
'11': translate('OpenLP.DB', 'RGB 1'),
'12': translate('OpenLP.DB', 'RGB 2'),
'13': translate('OpenLP.DB', 'RGB 3'),
'14': translate('OpenLP.DB', 'RGB 4'),
'15': translate('OpenLP.DB', 'RGB 5'),
'16': translate('OpenLP.DB', 'RGB 6'),
'17': translate('OpenLP.DB', 'RGB 7'),
'18': translate('OpenLP.DB', 'RGB 8'),
'19': translate('OpenLP.DB', 'RGB 9'),
'21': translate('OpenLP.DB', 'Video 1'),
'22': translate('OpenLP.DB', 'Video 2'),
'23': translate('OpenLP.DB', 'Video 3'),
'24': translate('OpenLP.DB', 'Video 4'),
'25': translate('OpenLP.DB', 'Video 5'),
'26': translate('OpenLP.DB', 'Video 6'),
'27': translate('OpenLP.DB', 'Video 7'),
'28': translate('OpenLP.DB', 'Video 8'),
'29': translate('OpenLP.DB', 'Video 9'),
'31': translate('OpenLP.DB', 'Digital 1'),
'32': translate('OpenLP.DB', 'Digital 2'),
'33': translate('OpenLP.DB', 'Digital 3'),
'34': translate('OpenLP.DB', 'Digital 4'),
'35': translate('OpenLP.DB', 'Digital 5'),
'36': translate('OpenLP.DB', 'Digital 6'),
'37': translate('OpenLP.DB', 'Digital 7'),
'38': translate('OpenLP.DB', 'Digital 8'),
'39': translate('OpenLP.DB', 'Digital 9'),
'41': translate('OpenLP.DB', 'Storage 1'),
'42': translate('OpenLP.DB', 'Storage 2'),
'43': translate('OpenLP.DB', 'Storage 3'),
'44': translate('OpenLP.DB', 'Storage 4'),
'45': translate('OpenLP.DB', 'Storage 5'),
'46': translate('OpenLP.DB', 'Storage 6'),
'47': translate('OpenLP.DB', 'Storage 7'),
'48': translate('OpenLP.DB', 'Storage 8'),
'49': translate('OpenLP.DB', 'Storage 9'),
'51': translate('OpenLP.DB', 'Network 1'),
'52': translate('OpenLP.DB', 'Network 2'),
'53': translate('OpenLP.DB', 'Network 3'),
'54': translate('OpenLP.DB', 'Network 4'),
'55': translate('OpenLP.DB', 'Network 5'),
'56': translate('OpenLP.DB', 'Network 6'),
'57': translate('OpenLP.DB', 'Network 7'),
'58': translate('OpenLP.DB', 'Network 8'),
'59': translate('OpenLP.DB', 'Network 9')
}

View File

@ -46,15 +46,19 @@ __all__ = ['PJLink1']
from codecs import decode
from PyQt5.QtCore import pyqtSignal, pyqtSlot
from PyQt5.QtNetwork import QAbstractSocket, QTcpSocket
from PyQt5 import QtCore, QtNetwork
from openlp.core.common import translate, qmd5_hash
from openlp.core.lib.projector.constants import *
from openlp.core.lib.projector.constants import CONNECTION_ERRORS, CR, ERROR_MSG, ERROR_STRING, \
E_AUTHENTICATION, E_CONNECTION_REFUSED, E_GENERAL, E_INVALID_DATA, E_NETWORK, E_NOT_CONNECTED, \
E_PARAMETER, E_PROJECTOR, E_SOCKET_TIMEOUT, E_UNAVAILABLE, E_UNDEFINED, PJLINK_ERRORS, \
PJLINK_ERST_STATUS, PJLINK_MAX_PACKET, PJLINK_PORT, PJLINK_POWR_STATUS, PJLINK_VALID_CMD, \
STATUS_STRING, S_CONNECTED, S_CONNECTING, S_NETWORK_RECEIVED, S_NETWORK_SENDING, S_NOT_CONNECTED, \
S_OFF, S_OK, S_ON, S_STATUS
# Shortcuts
SocketError = QAbstractSocket.SocketError
SocketSTate = QAbstractSocket.SocketState
SocketError = QtNetwork.QAbstractSocket.SocketError
SocketSTate = QtNetwork.QAbstractSocket.SocketState
PJLINK_PREFIX = '%'
PJLINK_CLASS = '1'
@ -62,18 +66,18 @@ PJLINK_HEADER = '{prefix}{linkclass}'.format(prefix=PJLINK_PREFIX, linkclass=PJL
PJLINK_SUFFIX = CR
class PJLink1(QTcpSocket):
class PJLink1(QtNetwork.QTcpSocket):
"""
Socket service for connecting to a PJLink-capable projector.
"""
# Signals sent by this module
changeStatus = pyqtSignal(str, int, str)
projectorNetwork = pyqtSignal(int) # Projector network activity
projectorStatus = pyqtSignal(int) # Status update
projectorAuthentication = pyqtSignal(str) # Authentication error
projectorNoAuthentication = pyqtSignal(str) # PIN set and no authentication needed
projectorReceivedData = pyqtSignal() # Notify when received data finished processing
projectorUpdateIcons = pyqtSignal() # Update the status icons on toolbar
changeStatus = QtCore.pyqtSignal(str, int, str)
projectorNetwork = QtCore.pyqtSignal(int) # Projector network activity
projectorStatus = QtCore.pyqtSignal(int) # Status update
projectorAuthentication = QtCore.pyqtSignal(str) # Authentication error
projectorNoAuthentication = QtCore.pyqtSignal(str) # PIN set and no authentication needed
projectorReceivedData = QtCore.pyqtSignal() # Notify when received data finished processing
projectorUpdateIcons = QtCore.pyqtSignal() # Update the status icons on toolbar
def __init__(self, name=None, ip=None, port=PJLINK_PORT, pin=None, *args, **kwargs):
"""
@ -116,8 +120,8 @@ class PJLink1(QTcpSocket):
self.error_status = S_OK
# Socket information
# Add enough space to input buffer for extraneous \n \r
self.maxSize = PJLINK_MAX_PACKET + 2
self.setReadBufferSize(self.maxSize)
self.max_size = PJLINK_MAX_PACKET + 2
self.setReadBufferSize(self.max_size)
# PJLink information
self.pjlink_class = '1' # Default class
self.reset_information()
@ -129,19 +133,20 @@ class PJLink1(QTcpSocket):
# Socket timer for some possible brain-dead projectors or network cable pulled
self.socket_timer = None
# Map command to function
self.PJLINK1_FUNC = {'AVMT': self.process_avmt,
'CLSS': self.process_clss,
'ERST': self.process_erst,
'INFO': self.process_info,
'INF1': self.process_inf1,
'INF2': self.process_inf2,
'INPT': self.process_inpt,
'INST': self.process_inst,
'LAMP': self.process_lamp,
'NAME': self.process_name,
'PJLINK': self.check_login,
'POWR': self.process_powr
}
self.pjlink1_functions = {
'AVMT': self.process_avmt,
'CLSS': self.process_clss,
'ERST': self.process_erst,
'INFO': self.process_info,
'INF1': self.process_inf1,
'INF2': self.process_inf2,
'INPT': self.process_inpt,
'INST': self.process_inst,
'LAMP': self.process_lamp,
'NAME': self.process_name,
'PJLINK': self.check_login,
'POWR': self.process_powr
}
def reset_information(self):
"""
@ -291,7 +296,7 @@ class PJLink1(QTcpSocket):
message=status_message if msg is None else msg))
self.changeStatus.emit(self.ip, status, message)
@pyqtSlot()
@QtCore.pyqtSlot()
def check_login(self, data=None):
"""
Processes the initial connection and authentication (if needed).
@ -309,8 +314,8 @@ class PJLink1(QTcpSocket):
log.error('({ip}) Socket timeout waiting for login'.format(ip=self.ip))
self.change_status(E_SOCKET_TIMEOUT)
return
read = self.readLine(self.maxSize)
dontcare = self.readLine(self.maxSize) # Clean out the trailing \r\n
read = self.readLine(self.max_size)
dontcare = self.readLine(self.max_size) # Clean out the trailing \r\n
if read is None:
log.warning('({ip}) read is None - socket error?'.format(ip=self.ip))
return
@ -320,7 +325,7 @@ class PJLink1(QTcpSocket):
data = decode(read, 'ascii')
# Possibility of extraneous data on input when reading.
# Clean out extraneous characters in buffer.
dontcare = self.readLine(self.maxSize)
dontcare = self.readLine(self.max_size)
log.debug('({ip}) check_login() read "{data}"'.format(ip=self.ip, data=data.strip()))
# At this point, we should only have the initial login prompt with
# possible authentication
@ -379,7 +384,7 @@ class PJLink1(QTcpSocket):
self.timer.setInterval(2000) # Set 2 seconds for initial information
self.timer.start()
@pyqtSlot()
@QtCore.pyqtSlot()
def get_data(self):
"""
Socket interface to retrieve data.
@ -389,7 +394,7 @@ class PJLink1(QTcpSocket):
log.debug('({ip}) get_data(): Not connected - returning'.format(ip=self.ip))
self.send_busy = False
return
read = self.readLine(self.maxSize)
read = self.readLine(self.max_size)
if read == -1:
# No data available
log.debug('({ip}) get_data(): No data available (-1)'.format(ip=self.ip))
@ -436,7 +441,7 @@ class PJLink1(QTcpSocket):
return
return self.process_command(cmd, data)
@pyqtSlot(QAbstractSocket.SocketError)
@QtCore.pyqtSlot(QtNetwork.QAbstractSocket.SocketError)
def get_error(self, err):
"""
Process error from SocketError signal.
@ -504,7 +509,7 @@ class PJLink1(QTcpSocket):
log.debug('({ip}) send_command() calling _send_string()'.format(ip=self.ip))
self._send_command()
@pyqtSlot()
@QtCore.pyqtSlot()
def _send_command(self, data=None):
"""
Socket interface to send data. If data=None, then check queue.
@ -586,8 +591,8 @@ class PJLink1(QTcpSocket):
self.projectorReceivedData.emit()
return
if cmd in self.PJLINK1_FUNC:
self.PJLINK1_FUNC[cmd](data)
if cmd in self.pjlink1_functions:
self.pjlink1_functions[cmd](data)
else:
log.warning('({ip}) Invalid command {data}'.format(ip=self.ip, data=cmd))
self.send_busy = False
@ -812,9 +817,9 @@ class PJLink1(QTcpSocket):
log.warning('({ip}) connect_to_host(): Already connected - returning'.format(ip=self.ip))
return
self.change_status(S_CONNECTING)
self.connectToHost(self.ip, self.port if type(self.port) is int else int(self.port))
self.connectToHost(self.ip, self.port if isinstance(self.port, int) else int(self.port))
@pyqtSlot()
@QtCore.pyqtSlot()
def disconnect_from_host(self, abort=False):
"""
Close socket and cleanup.

View File

@ -30,8 +30,6 @@ log = logging.getLogger(__name__)
log.debug('editform loaded')
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtCore import pyqtSlot, pyqtSignal
from PyQt5.QtWidgets import QDialog, QPlainTextEdit, QLineEdit, QDialogButtonBox, QLabel, QGridLayout
from openlp.core.common import translate, verify_ip_address
from openlp.core.lib import build_icon
@ -53,56 +51,56 @@ class Ui_ProjectorEditForm(object):
edit_projector_dialog.setMinimumWidth(400)
edit_projector_dialog.setModal(True)
# Define the basic layout
self.dialog_layout = QGridLayout(edit_projector_dialog)
self.dialog_layout = QtWidgets.QGridLayout(edit_projector_dialog)
self.dialog_layout.setObjectName('dialog_layout')
self.dialog_layout.setSpacing(8)
self.dialog_layout.setContentsMargins(8, 8, 8, 8)
# IP Address
self.ip_label = QLabel(edit_projector_dialog)
self.ip_label = QtWidgets.QLabel(edit_projector_dialog)
self.ip_label.setObjectName('projector_edit_ip_label')
self.ip_text = QLineEdit(edit_projector_dialog)
self.ip_text = QtWidgets.QLineEdit(edit_projector_dialog)
self.ip_text.setObjectName('projector_edit_ip_text')
self.dialog_layout.addWidget(self.ip_label, 0, 0)
self.dialog_layout.addWidget(self.ip_text, 0, 1)
# Port number
self.port_label = QLabel(edit_projector_dialog)
self.port_label = QtWidgets.QLabel(edit_projector_dialog)
self.port_label.setObjectName('projector_edit_ip_label')
self.port_text = QLineEdit(edit_projector_dialog)
self.port_text = QtWidgets.QLineEdit(edit_projector_dialog)
self.port_text.setObjectName('projector_edit_port_text')
self.dialog_layout.addWidget(self.port_label, 1, 0)
self.dialog_layout.addWidget(self.port_text, 1, 1)
# PIN
self.pin_label = QLabel(edit_projector_dialog)
self.pin_label = QtWidgets.QLabel(edit_projector_dialog)
self.pin_label.setObjectName('projector_edit_pin_label')
self.pin_text = QLineEdit(edit_projector_dialog)
self.pin_text = QtWidgets.QLineEdit(edit_projector_dialog)
self.pin_label.setObjectName('projector_edit_pin_text')
self.dialog_layout.addWidget(self.pin_label, 2, 0)
self.dialog_layout.addWidget(self.pin_text, 2, 1)
# Name
self.name_label = QLabel(edit_projector_dialog)
self.name_label = QtWidgets.QLabel(edit_projector_dialog)
self.name_label.setObjectName('projector_edit_name_label')
self.name_text = QLineEdit(edit_projector_dialog)
self.name_text = QtWidgets.QLineEdit(edit_projector_dialog)
self.name_text.setObjectName('projector_edit_name_text')
self.dialog_layout.addWidget(self.name_label, 3, 0)
self.dialog_layout.addWidget(self.name_text, 3, 1)
# Location
self.location_label = QLabel(edit_projector_dialog)
self.location_label = QtWidgets.QLabel(edit_projector_dialog)
self.location_label.setObjectName('projector_edit_location_label')
self.location_text = QLineEdit(edit_projector_dialog)
self.location_text = QtWidgets.QLineEdit(edit_projector_dialog)
self.location_text.setObjectName('projector_edit_location_text')
self.dialog_layout.addWidget(self.location_label, 4, 0)
self.dialog_layout.addWidget(self.location_text, 4, 1)
# Notes
self.notes_label = QLabel(edit_projector_dialog)
self.notes_label = QtWidgets.QLabel(edit_projector_dialog)
self.notes_label.setObjectName('projector_edit_notes_label')
self.notes_text = QPlainTextEdit(edit_projector_dialog)
self.notes_text = QtWidgets.QPlainTextEdit(edit_projector_dialog)
self.notes_text.setObjectName('projector_edit_notes_text')
self.dialog_layout.addWidget(self.notes_label, 5, 0, alignment=QtCore.Qt.AlignTop)
self.dialog_layout.addWidget(self.notes_text, 5, 1)
# Time for the buttons
self.button_box = QDialogButtonBox(QDialogButtonBox.Help |
QDialogButtonBox.Save |
QDialogButtonBox.Cancel)
self.button_box = QtWidgets.QDialogButtonBox(QtWidgets.QDialogButtonBox.Help |
QtWidgets.QDialogButtonBox.Save |
QtWidgets.QDialogButtonBox.Cancel)
self.dialog_layout.addWidget(self.button_box, 8, 0, 1, 2)
def retranslateUi(self, edit_projector_dialog):
@ -128,7 +126,7 @@ class Ui_ProjectorEditForm(object):
self.notes_text.insertPlainText(self.projector.notes)
class ProjectorEditForm(QDialog, Ui_ProjectorEditForm):
class ProjectorEditForm(QtWidgets.QDialog, Ui_ProjectorEditForm):
"""
Class to add or edit a projector entry in the database.
@ -140,8 +138,8 @@ class ProjectorEditForm(QDialog, Ui_ProjectorEditForm):
location = Column(String(30))
notes = Column(String(200))
"""
newProjector = pyqtSignal(str)
editProjector = pyqtSignal(object)
newProjector = QtCore.pyqtSignal(str)
editProjector = QtCore.pyqtSignal(object)
def __init__(self, parent=None, projectordb=None):
super(ProjectorEditForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint)
@ -159,10 +157,10 @@ class ProjectorEditForm(QDialog, Ui_ProjectorEditForm):
self.projector = projector
self.new_projector = False
self.retranslateUi(self)
reply = QDialog.exec(self)
reply = QtWidgets.QDialog.exec(self)
return reply
@pyqtSlot()
@QtCore.pyqtSlot()
def accept_me(self):
"""
Validate input before accepting input.
@ -247,14 +245,14 @@ class ProjectorEditForm(QDialog, Ui_ProjectorEditForm):
self.editProjector.emit(self.projector)
self.close()
@pyqtSlot()
@QtCore.pyqtSlot()
def help_me(self):
"""
Show a help message about the input fields.
"""
log.debug('help_me() signal received')
@pyqtSlot()
@QtCore.pyqtSlot()
def cancel_me(self):
"""
Cancel button clicked - just close.

View File

@ -26,43 +26,46 @@
"""
import logging
log = logging.getLogger(__name__)
log.debug('projectormanager loaded')
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import QObject, QThread, pyqtSlot
from PyQt5.QtWidgets import QWidget
from openlp.core.common import RegistryProperties, Settings, OpenLPMixin, \
RegistryMixin, translate
from openlp.core.ui.lib import OpenLPToolbar
from openlp.core.lib.ui import create_widget_action
from openlp.core.lib.projector import DialogSourceStyle
from openlp.core.lib.projector.constants import *
from openlp.core.lib.projector.constants import ERROR_MSG, ERROR_STRING, E_AUTHENTICATION, E_ERROR, \
E_NETWORK, E_NOT_CONNECTED, E_UNKNOWN_SOCKET_ERROR, STATUS_STRING, S_CONNECTED, S_CONNECTING, S_COOLDOWN, \
S_INITIALIZE, S_NOT_CONNECTED, S_OFF, S_ON, S_STANDBY, S_WARMUP
from openlp.core.lib.projector.db import ProjectorDB
from openlp.core.lib.projector.pjlink1 import PJLink1
from openlp.core.ui.projector.editform import ProjectorEditForm
from openlp.core.ui.projector.sourceselectform import SourceSelectTabs, SourceSelectSingle
log = logging.getLogger(__name__)
log.debug('projectormanager loaded')
# Dict for matching projector status to display icon
STATUS_ICONS = {S_NOT_CONNECTED: ':/projector/projector_item_disconnect.png',
S_CONNECTING: ':/projector/projector_item_connect.png',
S_CONNECTED: ':/projector/projector_off.png',
S_OFF: ':/projector/projector_off.png',
S_INITIALIZE: ':/projector/projector_off.png',
S_STANDBY: ':/projector/projector_off.png',
S_WARMUP: ':/projector/projector_warmup.png',
S_ON: ':/projector/projector_on.png',
S_COOLDOWN: ':/projector/projector_cooldown.png',
E_ERROR: ':/projector/projector_error.png',
E_NETWORK: ':/projector/projector_not_connected_error.png',
E_AUTHENTICATION: ':/projector/projector_not_connected_error.png',
E_UNKNOWN_SOCKET_ERROR: ':/projector/projector_not_connected_error.png',
E_NOT_CONNECTED: ':/projector/projector_not_connected_error.png'
}
STATUS_ICONS = {
S_NOT_CONNECTED: ':/projector/projector_item_disconnect.png',
S_CONNECTING: ':/projector/projector_item_connect.png',
S_CONNECTED: ':/projector/projector_off.png',
S_OFF: ':/projector/projector_off.png',
S_INITIALIZE: ':/projector/projector_off.png',
S_STANDBY: ':/projector/projector_off.png',
S_WARMUP: ':/projector/projector_warmup.png',
S_ON: ':/projector/projector_on.png',
S_COOLDOWN: ':/projector/projector_cooldown.png',
E_ERROR: ':/projector/projector_error.png',
E_NETWORK: ':/projector/projector_not_connected_error.png',
E_AUTHENTICATION: ':/projector/projector_not_connected_error.png',
E_UNKNOWN_SOCKET_ERROR: ':/projector/projector_not_connected_error.png',
E_NOT_CONNECTED: ':/projector/projector_not_connected_error.png'
}
class Ui_ProjectorManager(object):
class UiProjectorManager(object):
"""
UI part of the Projector Manager
"""
@ -271,7 +274,7 @@ class Ui_ProjectorManager(object):
self.update_icons()
class ProjectorManager(OpenLPMixin, RegistryMixin, QWidget, Ui_ProjectorManager, RegistryProperties):
class ProjectorManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, UiProjectorManager, RegistryProperties):
"""
Manage the projectors.
"""
@ -720,7 +723,7 @@ class ProjectorManager(OpenLPMixin, RegistryMixin, QWidget, Ui_ProjectorManager,
widget.setData(QtCore.Qt.UserRole, item)
item.link.db_item = item.db_item
item.widget = widget
thread = QThread(parent=self)
thread = QtCore.QThread(parent=self)
thread.my_parent = self
item.moveToThread(thread)
thread.started.connect(item.link.thread_started)
@ -751,7 +754,7 @@ class ProjectorManager(OpenLPMixin, RegistryMixin, QWidget, Ui_ProjectorManager,
for item in self.projector_list:
log.debug('New projector list - item: ({ip}) {name}'.format(ip=item.link.ip, name=item.link.name))
@pyqtSlot(str)
@QtCore.pyqtSlot(str)
def add_projector_from_wizard(self, ip, opts=None):
"""
Add a projector from the edit dialog
@ -763,7 +766,7 @@ class ProjectorManager(OpenLPMixin, RegistryMixin, QWidget, Ui_ProjectorManager,
item = self.projectordb.get_projector_by_ip(ip)
self.add_projector(item)
@pyqtSlot(object)
@QtCore.pyqtSlot(object)
def edit_projector_from_wizard(self, projector):
"""
Update projector from the wizard edit page
@ -796,7 +799,7 @@ class ProjectorManager(OpenLPMixin, RegistryMixin, QWidget, Ui_ProjectorManager,
"""
return self.projector_list
@pyqtSlot(str, int, str)
@QtCore.pyqtSlot(str, int, str)
def update_status(self, ip, status=None, msg=None):
"""
Update the status information/icon for selected list item
@ -846,7 +849,7 @@ class ProjectorManager(OpenLPMixin, RegistryMixin, QWidget, Ui_ProjectorManager,
item.setVisible(False if hidden else True)
item.setEnabled(True if enabled else False)
@pyqtSlot()
@QtCore.pyqtSlot()
def update_icons(self):
"""
Update the icons when the selected projectors change
@ -919,7 +922,7 @@ class ProjectorManager(OpenLPMixin, RegistryMixin, QWidget, Ui_ProjectorManager,
self.get_toolbar_item('blank_projector_multiple', hidden=False, enabled=True)
self.get_toolbar_item('show_projector_multiple', hidden=False, enabled=True)
@pyqtSlot(str)
@QtCore.pyqtSlot(str)
def authentication_error(self, name):
"""
Display warning dialog when attempting to connect with invalid pin
@ -933,7 +936,7 @@ class ProjectorManager(OpenLPMixin, RegistryMixin, QWidget, Ui_ProjectorManager,
'<br /><br />Please verify your PIN setting '
'for projector item "{name}"'.format(name=name))
@pyqtSlot(str)
@QtCore.pyqtSlot(str)
def no_authentication_error(self, name):
"""
Display warning dialog when pin saved for item but projector does not
@ -949,7 +952,7 @@ class ProjectorManager(OpenLPMixin, RegistryMixin, QWidget, Ui_ProjectorManager,
'for projector item "{name}"'.format(name=name))
class ProjectorItem(QObject):
class ProjectorItem(QtCore.QObject):
"""
Class for the projector list widget item.
NOTE: Actual PJLink class instance should be saved as self.link

View File

@ -20,24 +20,21 @@
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
:mod: `openlp.core.ui.projector.sourceselectform` module
:mod: `openlp.core.ui.projector.sourceselectform` module
Provides the dialog window for selecting video source for projector.
Provides the dialog window for selecting video source for projector.
"""
import logging
log = logging.getLogger(__name__)
log.debug('editform loaded')
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtCore import pyqtSlot, QSize
from PyQt5.QtWidgets import QAbstractButton, QDialog, QButtonGroup, QDialogButtonBox, QFormLayout, QLineEdit, \
QRadioButton, QStyle, QStylePainter, QStyleOptionTab, QTabBar, QTabWidget, QVBoxLayout, QWidget
from openlp.core.common import translate, is_macosx
from openlp.core.lib import build_icon
from openlp.core.lib.projector.db import ProjectorSource
from openlp.core.lib.projector.constants import PJLINK_DEFAULT_SOURCES, PJLINK_DEFAULT_CODES
log = logging.getLogger(__name__)
def source_group(inputs, source_text):
"""
@ -78,7 +75,7 @@ def source_group(inputs, source_text):
return keydict
def Build_Tab(group, source_key, default, projector, projectordb, edit=False):
def build_tab(group, source_key, default, projector, projectordb, edit=False):
"""
Create the radio button page for a tab.
Dictionary will be a 1-key entry where key=tab to setup, val=list of inputs.
@ -104,8 +101,8 @@ def Build_Tab(group, source_key, default, projector, projectordb, edit=False):
:param edit: If we're editing the source text
"""
buttonchecked = False
widget = QWidget()
layout = QFormLayout() if edit else QVBoxLayout()
widget = QtWidgets.QWidget()
layout = QtWidgets.QFormLayout() if edit else QtWidgets.QVBoxLayout()
layout.setSpacing(10)
widget.setLayout(layout)
tempkey = list(source_key.keys())[0] # Should only be 1 key
@ -114,7 +111,7 @@ def Build_Tab(group, source_key, default, projector, projectordb, edit=False):
button_count = len(sourcelist)
if edit:
for key in sourcelist:
item = QLineEdit()
item = QtWidgets.QLineEdit()
item.setObjectName('source_key_{key}'.format(key=key))
source_item = projectordb.get_source_by_code(code=key, projector_id=projector.db_item.id)
if source_item is None:
@ -130,7 +127,7 @@ def Build_Tab(group, source_key, default, projector, projectordb, edit=False):
text = source_key[tempkey][key]
else:
text = source_item.text
itemwidget = QRadioButton(text)
itemwidget = QtWidgets.QRadioButton(text)
itemwidget.setAutoExclusive(True)
if default == key:
itemwidget.setChecked(True)
@ -148,23 +145,23 @@ def set_button_tooltip(button_bar):
:param button_bar: QDialogButtonBar instance to update
"""
for button in button_bar.buttons():
if button_bar.standardButton(button) == QDialogButtonBox.Cancel:
if button_bar.standardButton(button) == QtWidgets.QDialogButtonBox.Cancel:
button.setToolTip(translate('OpenLP.SourceSelectForm',
'Ignoring current changes and return to OpenLP'))
elif button_bar.standardButton(button) == QDialogButtonBox.Reset:
elif button_bar.standardButton(button) == QtWidgets.QDialogButtonBox.Reset:
button.setToolTip(translate('OpenLP.SourceSelectForm',
'Delete all user-defined text and revert to PJLink default text'))
elif button_bar.standardButton(button) == QDialogButtonBox.Discard:
elif button_bar.standardButton(button) == QtWidgets.QDialogButtonBox.Discard:
button.setToolTip(translate('OpenLP.SourceSelectForm',
'Discard changes and reset to previous user-defined text'))
elif button_bar.standardButton(button) == QDialogButtonBox.Ok:
elif button_bar.standardButton(button) == QtWidgets.QDialogButtonBox.Ok:
button.setToolTip(translate('OpenLP.SourceSelectForm',
'Save changes and return to OpenLP'))
else:
log.debug('No tooltip for button {text}'.format(text=button.text()))
class FingerTabBarWidget(QTabBar):
class FingerTabBarWidget(QtWidgets.QTabBar):
"""
Realign west -orientation tabs to left-right text rather than south-north text
Borrowed from
@ -177,8 +174,8 @@ class FingerTabBarWidget(QTabBar):
:param width: Remove default width parameter in kwargs
:param height: Remove default height parameter in kwargs
"""
self.tabSize = QSize(kwargs.pop('width', 100), kwargs.pop('height', 25))
QTabBar.__init__(self, parent, *args, **kwargs)
self.tabSize = QtCore.QSize(kwargs.pop('width', 100), kwargs.pop('height', 25))
QtWidgets.QTabBar.__init__(self, parent, *args, **kwargs)
def paintEvent(self, event):
"""
@ -186,14 +183,14 @@ class FingerTabBarWidget(QTabBar):
:param event: Repaint event signal
"""
painter = QStylePainter(self)
option = QStyleOptionTab()
painter = QtWidgets.QStylePainter(self)
option = QtWidgets.QStyleOptionTab()
for index in range(self.count()):
self.initStyleOption(option, index)
tabRect = self.tabRect(index)
tabRect.moveLeft(10)
painter.drawControl(QStyle.CE_TabBarTabShape, option)
painter.drawControl(QtWidgets.QStyle.CE_TabBarTabShape, option)
painter.drawText(tabRect, QtCore.Qt.AlignVCenter |
QtCore.Qt.TextDontClip,
self.tabText(index))
@ -209,7 +206,7 @@ class FingerTabBarWidget(QTabBar):
return self.tabSize
class FingerTabWidget(QTabWidget):
class FingerTabWidget(QtWidgets.QTabWidget):
"""
A QTabWidget equivalent which uses our FingerTabBarWidget
@ -220,11 +217,11 @@ class FingerTabWidget(QTabWidget):
"""
Initialize FingerTabWidget instance
"""
QTabWidget.__init__(self, parent, *args)
QtWidgets.QTabWidget.__init__(self, parent, *args)
self.setTabBar(FingerTabBarWidget(self))
class SourceSelectTabs(QDialog):
class SourceSelectTabs(QtWidgets.QDialog):
"""
Class for handling selecting the source for the projector to use.
Uses tabbed interface.
@ -248,18 +245,18 @@ class SourceSelectTabs(QDialog):
self.setObjectName('source_select_tabs')
self.setWindowIcon(build_icon(':/icon/openlp-log-32x32.png'))
self.setModal(True)
self.layout = QVBoxLayout()
self.layout = QtWidgets.QVBoxLayout()
self.layout.setObjectName('source_select_tabs_layout')
if is_macosx():
self.tabwidget = QTabWidget(self)
self.tabwidget = QtWidgets.QTabWidget(self)
else:
self.tabwidget = FingerTabWidget(self)
self.tabwidget.setObjectName('source_select_tabs_tabwidget')
self.tabwidget.setUsesScrollButtons(False)
if is_macosx():
self.tabwidget.setTabPosition(QTabWidget.North)
self.tabwidget.setTabPosition(QtWidgets.QTabWidget.North)
else:
self.tabwidget.setTabPosition(QTabWidget.West)
self.tabwidget.setTabPosition(QtWidgets.QTabWidget.West)
self.layout.addWidget(self.tabwidget)
self.setLayout(self.layout)
@ -272,13 +269,12 @@ class SourceSelectTabs(QDialog):
self.projector = projector
self.source_text = self.projectordb.get_source_list(projector=projector)
self.source_group = source_group(projector.source_available, self.source_text)
# self.source_group = {'4': {'41': 'Storage 1'}, '5': {"51": 'Network 1'}}
self.button_group = [] if self.edit else QButtonGroup()
self.button_group = [] if self.edit else QtWidgets.QButtonGroup()
keys = list(self.source_group.keys())
keys.sort()
if self.edit:
for key in keys:
(tab, button_count, buttonchecked) = Build_Tab(group=self.button_group,
(tab, button_count, buttonchecked) = build_tab(group=self.button_group,
source_key={key: self.source_group[key]},
default=self.projector.source,
projector=self.projector,
@ -287,13 +283,13 @@ class SourceSelectTabs(QDialog):
thistab = self.tabwidget.addTab(tab, PJLINK_DEFAULT_SOURCES[key])
if buttonchecked:
self.tabwidget.setCurrentIndex(thistab)
self.button_box = QDialogButtonBox(QtWidgets.QDialogButtonBox.Reset |
QtWidgets.QDialogButtonBox.Discard |
QtWidgets.QDialogButtonBox.Ok |
QtWidgets.QDialogButtonBox.Cancel)
self.button_box = QtWidgets.QDialogButtonBox(QtWidgets.QDialogButtonBox.Reset |
QtWidgets.QDialogButtonBox.Discard |
QtWidgets.QDialogButtonBox.Ok |
QtWidgets.QDialogButtonBox.Cancel)
else:
for key in keys:
(tab, button_count, buttonchecked) = Build_Tab(group=self.button_group,
(tab, button_count, buttonchecked) = build_tab(group=self.button_group,
source_key={key: self.source_group[key]},
default=self.projector.source,
projector=self.projector,
@ -302,15 +298,15 @@ class SourceSelectTabs(QDialog):
thistab = self.tabwidget.addTab(tab, PJLINK_DEFAULT_SOURCES[key])
if buttonchecked:
self.tabwidget.setCurrentIndex(thistab)
self.button_box = QDialogButtonBox(QtWidgets.QDialogButtonBox.Ok |
QtWidgets.QDialogButtonBox.Cancel)
self.button_box = QtWidgets.QDialogButtonBox(QtWidgets.QDialogButtonBox.Ok |
QtWidgets.QDialogButtonBox.Cancel)
self.button_box.clicked.connect(self.button_clicked)
self.layout.addWidget(self.button_box)
set_button_tooltip(self.button_box)
selected = super(SourceSelectTabs, self).exec()
return selected
@pyqtSlot(object)
@QtCore.pyqtSlot(QtWidgets.QAbstractButton)
def button_clicked(self, button):
"""
Checks which button was clicked
@ -333,6 +329,9 @@ class SourceSelectTabs(QDialog):
return 100
def delete_sources(self):
"""
Delete the sources for this projector
"""
msg = QtWidgets.QMessageBox()
msg.setText(translate('OpenLP.SourceSelectForm', 'Delete entries for this projector'))
msg.setInformativeText(translate('OpenLP.SourceSelectForm',
@ -376,7 +375,7 @@ class SourceSelectTabs(QDialog):
self.done(selected)
class SourceSelectSingle(QDialog):
class SourceSelectSingle(QtWidgets.QDialog):
"""
Class for handling selecting the source for the projector to use.
Uses single dialog interface.
@ -407,12 +406,12 @@ class SourceSelectSingle(QDialog):
:param projector: Projector instance to build source list from
"""
self.projector = projector
self.layout = QFormLayout() if self.edit else QVBoxLayout()
self.layout = QtWidgets.QFormLayout() if self.edit else QtWidgets.QVBoxLayout()
self.layout.setObjectName('source_select_tabs_layout')
self.layout.setSpacing(10)
self.setLayout(self.layout)
self.setMinimumWidth(350)
self.button_group = [] if self.edit else QButtonGroup()
self.button_group = [] if self.edit else QtWidgets.QButtonGroup()
self.source_text = self.projectordb.get_source_list(projector=projector)
keys = list(self.source_text.keys())
keys.sort()
@ -420,7 +419,7 @@ class SourceSelectSingle(QDialog):
button_list = []
if self.edit:
for key in keys:
item = QLineEdit()
item = QtWidgets.QLineEdit()
item.setObjectName('source_key_{key}'.format(key=key))
source_item = self.projectordb.get_source_by_code(code=key, projector_id=self.projector.db_item.id)
if source_item is None:
@ -430,10 +429,10 @@ class SourceSelectSingle(QDialog):
item.setText(source_item.text)
self.layout.addRow(PJLINK_DEFAULT_CODES[key], item)
self.button_group.append(item)
self.button_box = QDialogButtonBox(QtWidgets.QDialogButtonBox.Reset |
QtWidgets.QDialogButtonBox.Discard |
QtWidgets.QDialogButtonBox.Ok |
QtWidgets.QDialogButtonBox.Cancel)
self.button_box = QtWidgets.QDialogButtonBox(QtWidgets.QDialogButtonBox.Reset |
QtWidgets.QDialogButtonBox.Discard |
QtWidgets.QDialogButtonBox.Ok |
QtWidgets.QDialogButtonBox.Cancel)
else:
for key in keys:
source_text = self.projectordb.get_source_by_code(code=key, projector_id=self.projector.db_item.id)
@ -443,8 +442,8 @@ class SourceSelectSingle(QDialog):
self.layout.addWidget(button)
self.button_group.addButton(button, int(key))
button_list.append(key)
self.button_box = QDialogButtonBox(QtWidgets.QDialogButtonBox.Ok |
QtWidgets.QDialogButtonBox.Cancel)
self.button_box = QtWidgets.QDialogButtonBox(QtWidgets.QDialogButtonBox.Ok |
QtWidgets.QDialogButtonBox.Cancel)
self.button_box.clicked.connect(self.button_clicked)
self.layout.addWidget(self.button_box)
self.setMinimumHeight(key_count * 25)
@ -452,7 +451,7 @@ class SourceSelectSingle(QDialog):
selected = super(SourceSelectSingle, self).exec()
return selected
@pyqtSlot(QAbstractButton)
@QtCore.pyqtSlot(QtWidgets.QAbstractButton)
def button_clicked(self, button):
"""
Checks which button was clicked
@ -488,7 +487,7 @@ class SourceSelectSingle(QDialog):
self.projectordb.delete_all_objects(ProjectorSource, ProjectorSource.projector_id == self.projector.db_item.id)
self.done(100)
@pyqtSlot()
@QtCore.pyqtSlot()
def accept_me(self):
"""
Slot to accept 'OK' button

View File

@ -22,14 +22,11 @@
import logging
from PyQt5 import QtWidgets
from openlp.core.api.http import register_endpoint
from openlp.core.common.actions import ActionList
from openlp.core.lib import Plugin, StringContent, build_icon, translate
from openlp.core.lib.ui import UiStrings, create_action
from openlp.plugins.bibles.endpoint import api_bibles_endpoint, bibles_endpoint
from openlp.plugins.bibles.forms import BibleUpgradeForm
from openlp.plugins.bibles.lib import BibleManager, BiblesTab, BibleMediaItem, LayoutStyle, DisplayStyle, \
LanguageSelection
from openlp.plugins.bibles.lib.mediaitem import BibleSearch
@ -94,7 +91,6 @@ class BiblePlugin(Plugin):
action_list.add_action(self.import_bible_item, UiStrings().Import)
# Set to invisible until we can export bibles
self.export_bible_item.setVisible(False)
self.tools_upgrade_item.setVisible(bool(self.manager.old_bible_databases))
def finalise(self):
"""
@ -108,20 +104,6 @@ class BiblePlugin(Plugin):
self.import_bible_item.setVisible(False)
self.export_bible_item.setVisible(False)
def app_startup(self):
"""
Perform tasks on application startup
"""
super(BiblePlugin, self).app_startup()
if self.manager.old_bible_databases:
if QtWidgets.QMessageBox.information(
self.main_window, translate('OpenLP', 'Information'),
translate('OpenLP', 'Bible format has changed.\nYou have to upgrade your '
'existing Bibles.\nShould OpenLP upgrade now?'),
QtWidgets.QMessageBox.StandardButtons(QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No)) == \
QtWidgets.QMessageBox.Yes:
self.on_tools_upgrade_item_triggered()
def add_import_menu_item(self, import_menu):
"""
Add an import menu item
@ -143,30 +125,6 @@ class BiblePlugin(Plugin):
text=translate('BiblesPlugin', '&Bible'), visible=False)
export_menu.addAction(self.export_bible_item)
def add_tools_menu_item(self, tools_menu):
"""
Give the bible plugin the opportunity to add items to the **Tools** menu.
:param tools_menu: The actual **Tools** menu item, so that your actions can use it as their parent.
"""
log.debug('add tools menu')
self.tools_upgrade_item = create_action(
tools_menu, 'toolsUpgradeItem',
text=translate('BiblesPlugin', '&Upgrade older Bibles'),
statustip=translate('BiblesPlugin', 'Upgrade the Bible databases to the latest format.'),
visible=False, triggers=self.on_tools_upgrade_item_triggered)
tools_menu.addAction(self.tools_upgrade_item)
def on_tools_upgrade_item_triggered(self):
"""
Upgrade older bible databases.
"""
if not hasattr(self, 'upgrade_wizard'):
self.upgrade_wizard = BibleUpgradeForm(self.main_window, self.manager, self)
# If the import was not cancelled then reload.
if self.upgrade_wizard.exec():
self.media_item.reload_bibles()
def on_bible_import_click(self):
"""
Show the Bible Import wizard

View File

@ -21,30 +21,12 @@
###############################################################################
"""
Forms in OpenLP are made up of two classes. One class holds all the graphical elements, like buttons and lists, and the
other class holds all the functional code, like slots and loading and saving.
The first class, commonly known as the **Dialog** class, is typically named ``Ui_<name>Dialog``. It is a slightly
modified version of the class that the ``pyuic5`` command produces from Qt5's .ui file. Typical modifications will be
converting most strings from "" to '' and using OpenLP's ``translate()`` function for translating strings.
The second class, commonly known as the **Form** class, is typically named ``<name>Form``. This class is the one which
is instantiated and used. It uses dual inheritance to inherit from (usually) QtWidgets.QDialog and the Ui class
mentioned above, like so::
class BibleImportForm(QtWidgets.QWizard, Ui_BibleImportWizard):
def __init__(self, parent, manager, bible_plugin):
super(BibleImportForm, self).__init__(parent)
self.setupUi(self)
This allows OpenLP to use ``self.object`` for all the GUI elements while keeping them separate from the functionality,
so that it is easier to recreate the GUI from the .ui files later if necessary.
The :mod:`forms` module contains all the ui functionality for the bibles
plugin.
"""
from .booknameform import BookNameForm
from .languageform import LanguageForm
from .bibleimportform import BibleImportForm
from .bibleupgradeform import BibleUpgradeForm
from .editbibleform import EditBibleForm
__all__ = ['BookNameForm', 'LanguageForm', 'BibleImportForm', 'BibleUpgradeForm', 'EditBibleForm']
__all__ = ['BookNameForm', 'LanguageForm', 'BibleImportForm', 'EditBibleForm']

View File

@ -40,7 +40,7 @@ from openlp.core.ui.lib.wizard import OpenLPWizard, WizardStrings
from openlp.core.common.languagemanager import get_locale_key
from openlp.plugins.bibles.lib.manager import BibleFormat
from openlp.plugins.bibles.lib.db import clean_filename
from openlp.plugins.bibles.lib.http import CWExtract, BGExtract, BSExtract
from openlp.plugins.bibles.lib.importers.http import CWExtract, BGExtract, BSExtract
log = logging.getLogger(__name__)

View File

@ -1,575 +0,0 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2016 OpenLP Developers #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
The bible import functions for OpenLP
"""
import logging
import os
import shutil
from tempfile import gettempdir
from PyQt5 import QtCore, QtWidgets
from openlp.core.common import Registry, AppLocation, UiStrings, Settings, check_directory_exists, translate, \
delete_file
from openlp.core.lib.ui import critical_error_message_box
from openlp.core.ui.lib.wizard import OpenLPWizard, WizardStrings
from openlp.plugins.bibles.lib.db import BibleDB, BibleMeta, OldBibleDB, BiblesResourcesDB
from openlp.plugins.bibles.lib.http import BSExtract, BGExtract, CWExtract
log = logging.getLogger(__name__)
class BibleUpgradeForm(OpenLPWizard):
"""
This is the Bible Upgrade Wizard, which allows easy importing of Bibles into OpenLP from older OpenLP2 database
versions.
"""
log.info('BibleUpgradeForm loaded')
def __init__(self, parent, manager, bible_plugin):
"""
Instantiate the wizard, and run any extra setup we need to.
:param parent: The QWidget-derived parent of the wizard.
:param manager: The Bible manager.
:param bible_plugin: The Bible plugin.
"""
self.manager = manager
self.media_item = bible_plugin.media_item
self.suffix = '.sqlite'
self.settings_section = 'bibles'
self.path = AppLocation.get_section_data_path(self.settings_section)
self.temp_dir = os.path.join(gettempdir(), 'openlp')
self.files = self.manager.old_bible_databases
self.success = {}
self.new_bibles = {}
super(BibleUpgradeForm, self).__init__(
parent, bible_plugin, 'bibleUpgradeWizard', ':/wizards/wizard_importbible.bmp')
def setupUi(self, image):
"""
Set up the UI for the bible wizard.
"""
super(BibleUpgradeForm, self).setupUi(image)
Registry().register_function('openlp_stop_wizard', self.stop_import)
def stop_import(self):
"""
Stops the import of the Bible.
"""
log.debug('Stopping import')
self.stop_import_flag = True
def reject(self):
"""
Stop the wizard on cancel button, close button or ESC key.
"""
log.debug('Wizard cancelled by user')
self.stop_import_flag = True
if not self.currentPage() == self.progress_page:
self.done(QtWidgets.QDialog.Rejected)
def onCurrentIdChanged(self, page_id):
"""
Perform necessary functions depending on which wizard page is active.
"""
if self.page(page_id) == self.progress_page:
self.pre_wizard()
self.perform_wizard()
self.post_wizard()
elif self.page(page_id) == self.selectPage and not self.files:
self.next()
def on_backup_browse_button_clicked(self):
"""
Show the file open dialog for the OSIS file.
"""
filename = QtWidgets.QFileDialog.getExistingDirectory(self, translate('BiblesPlugin.UpgradeWizardForm',
'Select a Backup Directory'), '')
if filename:
self.backupDirectoryEdit.setText(filename)
def on_no_backup_check_box_toggled(self, checked):
"""
Enable or disable the backup directory widgets.
"""
self.backupDirectoryEdit.setEnabled(not checked)
self.backupBrowseButton.setEnabled(not checked)
def backup_old_bibles(self, backup_directory):
"""
Backup old bible databases in a given folder.
"""
check_directory_exists(backup_directory)
success = True
for filename in self.files:
try:
shutil.copy(os.path.join(self.path, filename[0]), backup_directory)
except:
success = False
return success
def custom_init(self):
"""
Perform any custom initialisation for bible upgrading.
"""
self.manager.set_process_dialog(self)
self.restart()
def custom_signals(self):
"""
Set up the signals used in the bible importer.
"""
self.backupBrowseButton.clicked.connect(self.on_backup_browse_button_clicked)
self.noBackupCheckBox.toggled.connect(self.on_no_backup_check_box_toggled)
def add_custom_pages(self):
"""
Add the bible import specific wizard pages.
"""
# Backup Page
self.backup_page = QtWidgets.QWizardPage()
self.backup_page.setObjectName('BackupPage')
self.backupLayout = QtWidgets.QVBoxLayout(self.backup_page)
self.backupLayout.setObjectName('BackupLayout')
self.backupInfoLabel = QtWidgets.QLabel(self.backup_page)
self.backupInfoLabel.setOpenExternalLinks(True)
self.backupInfoLabel.setTextFormat(QtCore.Qt.RichText)
self.backupInfoLabel.setWordWrap(True)
self.backupInfoLabel.setObjectName('backupInfoLabel')
self.backupLayout.addWidget(self.backupInfoLabel)
self.selectLabel = QtWidgets.QLabel(self.backup_page)
self.selectLabel.setObjectName('select_label')
self.backupLayout.addWidget(self.selectLabel)
self.formLayout = QtWidgets.QFormLayout()
self.formLayout.setContentsMargins(0, 0, 0, 0)
self.formLayout.setObjectName('FormLayout')
self.backupDirectoryLabel = QtWidgets.QLabel(self.backup_page)
self.backupDirectoryLabel.setObjectName('backupDirectoryLabel')
self.backupDirectoryLayout = QtWidgets.QHBoxLayout()
self.backupDirectoryLayout.setObjectName('BackupDirectoryLayout')
self.backupDirectoryEdit = QtWidgets.QLineEdit(self.backup_page)
self.backupDirectoryEdit.setObjectName('BackupFolderEdit')
self.backupDirectoryLayout.addWidget(self.backupDirectoryEdit)
self.backupBrowseButton = QtWidgets.QToolButton(self.backup_page)
self.backupBrowseButton.setIcon(self.open_icon)
self.backupBrowseButton.setObjectName('BackupBrowseButton')
self.backupDirectoryLayout.addWidget(self.backupBrowseButton)
self.formLayout.addRow(self.backupDirectoryLabel, self.backupDirectoryLayout)
self.backupLayout.addLayout(self.formLayout)
self.noBackupCheckBox = QtWidgets.QCheckBox(self.backup_page)
self.noBackupCheckBox.setObjectName('NoBackupCheckBox')
self.backupLayout.addWidget(self.noBackupCheckBox)
self.spacer = QtWidgets.QSpacerItem(10, 0, QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Minimum)
self.backupLayout.addItem(self.spacer)
self.addPage(self.backup_page)
# Select Page
self.selectPage = QtWidgets.QWizardPage()
self.selectPage.setObjectName('SelectPage')
self.pageLayout = QtWidgets.QVBoxLayout(self.selectPage)
self.pageLayout.setObjectName('pageLayout')
self.scrollArea = QtWidgets.QScrollArea(self.selectPage)
self.scrollArea.setWidgetResizable(True)
self.scrollArea.setObjectName('scrollArea')
self.scrollArea.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.scrollAreaContents = QtWidgets.QWidget(self.scrollArea)
self.scrollAreaContents.setObjectName('scrollAreaContents')
self.formLayout = QtWidgets.QVBoxLayout(self.scrollAreaContents)
self.formLayout.setSpacing(2)
self.formLayout.setObjectName('formLayout')
self.addScrollArea()
self.pageLayout.addWidget(self.scrollArea)
self.addPage(self.selectPage)
def addScrollArea(self):
"""
Add the content to the scrollArea.
"""
self.checkBox = {}
for number, filename in enumerate(self.files):
bible = OldBibleDB(self.media_item, path=self.path, file=filename[0])
self.checkBox[number] = QtWidgets.QCheckBox(self.scrollAreaContents)
self.checkBox[number].setObjectName('checkBox[{count:d}]'.format(count=number))
self.checkBox[number].setText(bible.get_name())
self.checkBox[number].setCheckState(QtCore.Qt.Checked)
self.formLayout.addWidget(self.checkBox[number])
self.spacer_item = QtWidgets.QSpacerItem(20, 5, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.formLayout.addItem(self.spacer_item)
self.scrollArea.setWidget(self.scrollAreaContents)
def clearScrollArea(self):
"""
Remove the content from the scrollArea.
"""
for number, filename in enumerate(self.files):
self.formLayout.removeWidget(self.checkBox[number])
self.checkBox[number].setParent(None)
self.formLayout.removeItem(self.spacer_item)
def retranslateUi(self):
"""
Allow for localisation of the bible import wizard.
"""
self.setWindowTitle(translate('BiblesPlugin.UpgradeWizardForm', 'Bible Upgrade Wizard'))
self.title_label.setText(WizardStrings.HeaderStyle % translate('OpenLP.Ui',
'Welcome to the Bible Upgrade Wizard'))
self.information_label.setText(
translate('BiblesPlugin.UpgradeWizardForm',
'This wizard will help you to upgrade your existing Bibles from a prior version of OpenLP 2. '
'Click the next button below to start the upgrade process.'))
self.backup_page.setTitle(translate('BiblesPlugin.UpgradeWizardForm', 'Select Backup Directory'))
self.backup_page.setSubTitle(
translate('BiblesPlugin.UpgradeWizardForm', 'Please select a backup directory for your Bibles'))
self.backupInfoLabel.setText(
translate('BiblesPlugin.UpgradeWizardForm',
'Previous releases of OpenLP 2.0 are unable to use upgraded Bibles.'
' This will create a backup of your current Bibles so that you can '
'simply copy the files back to your OpenLP data directory if you '
'need to revert to a previous release of OpenLP. Instructions on '
'how to restore the files can be found in our <a href="'
'http://wiki.openlp.org/faq">Frequently Asked Questions</a>.'))
self.selectLabel.setText(
translate('BiblesPlugin.UpgradeWizardForm', 'Please select a backup location for your Bibles.'))
self.backupDirectoryLabel.setText(translate('BiblesPlugin.UpgradeWizardForm', 'Backup Directory:'))
self.noBackupCheckBox.setText(
translate('BiblesPlugin.UpgradeWizardForm', 'There is no need to backup my Bibles'))
self.selectPage.setTitle(translate('BiblesPlugin.UpgradeWizardForm', 'Select Bibles'))
self.selectPage.setSubTitle(
translate('BiblesPlugin.UpgradeWizardForm', 'Please select the Bibles to upgrade'))
self.progress_page.setTitle(translate('BiblesPlugin.UpgradeWizardForm', 'Upgrading'))
self.progress_page.setSubTitle(
translate('BiblesPlugin.UpgradeWizardForm', 'Please wait while your Bibles are upgraded.'))
self.progress_label.setText(WizardStrings.Ready)
self.progress_bar.setFormat('%p%')
def validateCurrentPage(self):
"""
Validate the current page before moving on to the next page.
"""
if self.currentPage() == self.welcome_page:
return True
elif self.currentPage() == self.backup_page:
if not self.noBackupCheckBox.checkState() == QtCore.Qt.Checked:
backup_path = self.backupDirectoryEdit.text()
if not backup_path:
critical_error_message_box(
UiStrings().EmptyField,
translate('BiblesPlugin.UpgradeWizardForm', 'You need to specify a backup directory for '
'your Bibles.'))
self.backupDirectoryEdit.setFocus()
return False
else:
if not self.backup_old_bibles(backup_path):
critical_error_message_box(
UiStrings().Error,
translate('BiblesPlugin.UpgradeWizardForm', 'The backup was not successful.\nTo backup '
'your Bibles you need permission to write to the given directory.'))
return False
return True
elif self.currentPage() == self.selectPage:
check_directory_exists(self.temp_dir)
for number, filename in enumerate(self.files):
if not self.checkBox[number].checkState() == QtCore.Qt.Checked:
continue
# Move bibles to temp dir.
if not os.path.exists(os.path.join(self.temp_dir, filename[0])):
shutil.move(os.path.join(self.path, filename[0]), self.temp_dir)
else:
delete_file(os.path.join(self.path, filename[0]))
return True
if self.currentPage() == self.progress_page:
return True
def set_defaults(self):
"""
Set default values for the wizard pages.
"""
log.debug('BibleUpgrade set_defaults')
settings = Settings()
settings.beginGroup(self.plugin.settings_section)
self.stop_import_flag = False
self.success.clear()
self.new_bibles.clear()
self.clearScrollArea()
self.files = self.manager.old_bible_databases
self.addScrollArea()
self.retranslateUi()
for number, filename in enumerate(self.files):
self.checkBox[number].setCheckState(QtCore.Qt.Checked)
self.progress_bar.show()
self.restart()
self.finish_button.setVisible(False)
self.cancel_button.setVisible(True)
settings.endGroup()
def pre_wizard(self):
"""
Prepare the UI for the upgrade.
"""
super(BibleUpgradeForm, self).pre_wizard()
self.progress_label.setText(translate('BiblesPlugin.UpgradeWizardForm', 'Starting upgrade...'))
self.application.process_events()
def perform_wizard(self):
"""
Perform the actual upgrade.
"""
self.includeWebBible = False
proxy_server = None
if not self.files:
self.progress_label.setText(
translate('BiblesPlugin.UpgradeWizardForm', 'There are no Bibles that need to be upgraded.'))
self.progress_bar.hide()
return
max_bibles = 0
for number, file in enumerate(self.files):
if self.checkBox[number].checkState() == QtCore.Qt.Checked:
max_bibles += 1
old_bible = None
for number, filename in enumerate(self.files):
# Close the previous bible's connection.
if old_bible is not None:
old_bible.close_connection()
# Set to None to make obvious that we have already closed the
# database.
old_bible = None
if self.stop_import_flag:
self.success[number] = False
break
if not self.checkBox[number].checkState() == QtCore.Qt.Checked:
self.success[number] = False
continue
self.progress_bar.reset()
old_bible = OldBibleDB(self.media_item, path=self.temp_dir, file=filename[0])
name = filename[1]
self.progress_label.setText(
translate('BiblesPlugin.UpgradeWizardForm',
'Upgrading Bible {count} of {total}: "{name}"\n'
'Upgrading ...').format(count=number + 1,
total=max_bibles,
name=name))
self.new_bibles[number] = BibleDB(self.media_item, path=self.path, name=name, file=filename[0])
self.new_bibles[number].register(self.plugin.upgrade_wizard)
metadata = old_bible.get_metadata()
web_bible = False
meta_data = {}
for meta in metadata:
# Upgrade the names of the metadata keys
if meta['key'] == 'Version':
meta['key'] = 'name'
if meta['key'] == 'Bookname language':
meta['key'] = 'book_name_language'
meta['key'] = meta['key'].lower().replace(' ', '_')
# Copy the metadata
meta_data[meta['key']] = meta['value']
if meta['key'] != 'name' and meta['key'] != 'dbversion':
self.new_bibles[number].save_meta(meta['key'], meta['value'])
if meta['key'] == 'download_source':
web_bible = True
self.includeWebBible = True
proxy_server = meta.get('proxy_server')
if web_bible:
if meta_data['download_source'].lower() == 'crosswalk':
handler = CWExtract(proxy_server)
elif meta_data['download_source'].lower() == 'biblegateway':
handler = BGExtract(proxy_server)
elif meta_data['download_source'].lower() == 'bibleserver':
handler = BSExtract(proxy_server)
books = handler.get_books_from_http(meta_data['download_name'])
if not books:
log.error('Upgrading books from {uri} - '
'download name: "{name}" failed'.format(uri=meta_data['download_source'],
name=meta_data['download_name']))
self.new_bibles[number].session.close()
del self.new_bibles[number]
critical_error_message_box(
translate('BiblesPlugin.UpgradeWizardForm', 'Download Error'),
translate('BiblesPlugin.UpgradeWizardForm',
'To upgrade your Web Bibles an Internet connection is required.'))
text = translate('BiblesPlugin.UpgradeWizardForm',
'Upgrading Bible {count} of {total}: "{name}"\n'
'Failed').format(count=number + 1, total=max_bibles, name=name)
self.increment_progress_bar(text, self.progress_bar.maximum() - self.progress_bar.value())
self.success[number] = False
continue
bible = BiblesResourcesDB.get_webbible(
meta_data['download_name'],
meta_data['download_source'].lower())
if bible and bible['language_id']:
language_id = bible['language_id']
self.new_bibles[number].save_meta('language_id', language_id)
else:
language_id = self.new_bibles[number].get_language(name)
if not language_id:
log.warning('Upgrading from "{name}" failed'.format(name=filename[0]))
self.new_bibles[number].session.close()
del self.new_bibles[number]
self.increment_progress_bar(
translate('BiblesPlugin.UpgradeWizardForm',
'Upgrading Bible {count} of {total}: "{name}"\n'
'Failed').format(count=number + 1, total=max_bibles, name=name),
self.progress_bar.maximum() - self.progress_bar.value())
self.success[number] = False
continue
self.progress_bar.setMaximum(len(books))
for book in books:
if self.stop_import_flag:
self.success[number] = False
break
self.increment_progress_bar(
translate('BiblesPlugin.UpgradeWizardForm',
'Upgrading Bible {count} of {total}: "{name}"\n'
'Upgrading {book} ...').format(count=number + 1, total=max_bibles,
name=name, book=book))
book_ref_id = self.new_bibles[number].\
get_book_ref_id_by_name(book, len(books), language_id)
if not book_ref_id:
log.warning('Upgrading books from {source} - download name: "{name}" '
'aborted by user'.format(source=meta_data['download_source'],
name=meta_data['download_name']))
self.new_bibles[number].session.close()
del self.new_bibles[number]
self.success[number] = False
break
book_details = BiblesResourcesDB.get_book_by_id(book_ref_id)
db_book = self.new_bibles[number].create_book(book, book_ref_id, book_details['testament_id'])
# Try to import already downloaded verses.
oldbook = old_bible.get_book(book)
if oldbook:
verses = old_bible.get_verses(oldbook['id'])
if not verses:
log.warning('No verses found to import for book "{book}"'.format(book=book))
continue
for verse in verses:
if self.stop_import_flag:
self.success[number] = False
break
self.new_bibles[number].create_verse(db_book.id, int(verse['chapter']),
int(verse['verse']), str(verse['text']))
self.application.process_events()
self.new_bibles[number].session.commit()
else:
language_id = self.new_bibles[number].get_object(BibleMeta, 'language_id')
if not language_id:
language_id = self.new_bibles[number].get_language(name)
if not language_id:
log.warning('Upgrading books from "{name}" failed'.format(name=name))
self.new_bibles[number].session.close()
del self.new_bibles[number]
self.increment_progress_bar(
translate('BiblesPlugin.UpgradeWizardForm',
'Upgrading Bible {count} of {total}: "{name}"\n'
'Failed').format(count=number + 1, total=max_bibles, name=name),
self.progress_bar.maximum() - self.progress_bar.value())
self.success[number] = False
continue
books = old_bible.get_books()
self.progress_bar.setMaximum(len(books))
for book in books:
if self.stop_import_flag:
self.success[number] = False
break
self.increment_progress_bar(
translate('BiblesPlugin.UpgradeWizardForm',
'Upgrading Bible {count} of {total}: "{name}"\n'
'Upgrading {book} ...').format(count=number + 1, total=max_bibles,
name=name, book=book['name']))
book_ref_id = self.new_bibles[number].get_book_ref_id_by_name(book['name'], len(books), language_id)
if not book_ref_id:
log.warning('Upgrading books from {name} " failed - aborted by user'.format(name=name))
self.new_bibles[number].session.close()
del self.new_bibles[number]
self.success[number] = False
break
book_details = BiblesResourcesDB.get_book_by_id(book_ref_id)
db_book = self.new_bibles[number].create_book(book['name'], book_ref_id,
book_details['testament_id'])
verses = old_bible.get_verses(book['id'])
if not verses:
log.warning('No verses found to import for book "{book}"'.format(book=book['name']))
self.new_bibles[number].delete_book(db_book)
continue
for verse in verses:
if self.stop_import_flag:
self.success[number] = False
break
self.new_bibles[number].create_verse(db_book.id, int(verse['chapter']), int(verse['verse']),
str(verse['text']))
self.application.process_events()
self.new_bibles[number].session.commit()
if not self.success.get(number, True):
self.increment_progress_bar(
translate('BiblesPlugin.UpgradeWizardForm',
'Upgrading Bible {count} of {total}: "{name}"\n'
'Failed').format(count=number + 1, total=max_bibles, name=name),
self.progress_bar.maximum() - self.progress_bar.value())
else:
self.success[number] = True
self.new_bibles[number].save_meta('name', name)
self.increment_progress_bar(
translate('BiblesPlugin.UpgradeWizardForm',
'Upgrading Bible {count} of {total}: "{name}"\n'
'Complete').format(count=number + 1, total=max_bibles, name=name))
if number in self.new_bibles:
self.new_bibles[number].session.close()
# Close the last bible's connection if possible.
if old_bible is not None:
old_bible.close_connection()
def post_wizard(self):
"""
Clean up the UI after the import has finished.
"""
successful_import = 0
failed_import = 0
for number, filename in enumerate(self.files):
if self.success.get(number):
successful_import += 1
elif self.checkBox[number].checkState() == QtCore.Qt.Checked:
failed_import += 1
# Delete upgraded (but not complete, corrupted, ...) bible.
delete_file(os.path.join(self.path, filename[0]))
# Copy not upgraded bible back.
shutil.move(os.path.join(self.temp_dir, filename[0]), self.path)
if failed_import > 0:
failed_import_text = translate('BiblesPlugin.UpgradeWizardForm',
', {name} failed').format(name=failed_import)
else:
failed_import_text = ''
if successful_import > 0:
if self.includeWebBible:
self.progress_label.setText(
translate('BiblesPlugin.UpgradeWizardForm',
'Upgrading Bible(s): {count:d} successful{failed}\nPlease note that verses '
'from Web Bibles will be downloaded on demand and so an Internet connection is required.'
).format(count=successful_import, failed=failed_import_text))
else:
self.progress_label.setText(
translate('BiblesPlugin.UpgradeWizardForm',
'Upgrading Bible(s): {count:d} successful{failed}').format(count=successful_import,
failed=failed_import_text))
else:
self.progress_label.setText(translate('BiblesPlugin.UpgradeWizardForm', 'Upgrade failed.'))
# Remove temp directory.
shutil.rmtree(self.temp_dir, True)
super(BibleUpgradeForm, self).post_wizard()

View File

@ -0,0 +1,113 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2016 OpenLP Developers #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
import logging
from lxml import etree, objectify
from openlp.core.common import OpenLPMixin, languages
from openlp.core.lib import ValidationError
from openlp.plugins.bibles.lib.db import BibleDB, BiblesResourcesDB
log = logging.getLogger(__name__)
class BibleImport(OpenLPMixin, BibleDB):
"""
Helper class to import bibles from a third party source into OpenLP
"""
# TODO: Test
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.filename = kwargs['filename'] if 'filename' in kwargs else None
def get_language_id(self, file_language=None, bible_name=None):
"""
Get the language_id for the language of the bible. Fallback to user input if we cannot do this pragmatically.
:param file_language: Language of the bible. Possibly retrieved from the file being imported. Str
:param bible_name: Name of the bible to display on the get_language dialog. Str
:return: The id of a language Int or None
"""
language_id = None
if file_language:
language = languages.get_language(file_language)
if language and language.id:
language_id = language.id
if not language_id:
# The language couldn't be detected, ask the user
language_id = self.get_language(bible_name)
if not language_id:
# User cancelled get_language dialog
log.error('Language detection failed when importing from "{name}". User aborted language selection.'
.format(name=bible_name))
return None
self.save_meta('language_id', language_id)
return language_id
def find_and_create_book(self, name, no_of_books, language_id, guess_id=None):
"""
Find the OpenLP book id and then create the book in this bible db
:param name: Name of the book. If None, then fall back to the guess_id Str
:param no_of_books: The total number of books contained in this bible Int
:param language_id: The OpenLP id of the language of this bible Int
:param guess_id: The guessed id of the book, used if name is None Int
:return:
"""
if name:
book_ref_id = self.get_book_ref_id_by_name(name, no_of_books, language_id)
else:
log.debug('No book name supplied. Falling back to guess_id')
book_ref_id = guess_id
if not book_ref_id:
raise ValidationError(msg='Could not resolve book_ref_id in "{}"'.format(self.filename))
book_details = BiblesResourcesDB.get_book_by_id(book_ref_id)
if book_details is None:
raise ValidationError(msg='book_ref_id: {book_ref} Could not be found in the BibleResourcesDB while '
'importing {file}'.format(book_ref=book_ref_id, file=self.filename))
return self.create_book(name, book_ref_id, book_details['testament_id'])
@staticmethod
def parse_xml(filename, use_objectify=False, elements=None, tags=None):
"""
Parse and clean the supplied file by removing any elements or tags we don't use.
:param filename: The filename of the xml file to parse. Str
:param use_objectify: Use the objectify parser rather than the etree parser. (Bool)
:param elements: A tuple of element names (Str) to remove along with their content.
:param tags: A tuple of element names (Str) to remove, preserving their content.
:return: The root element of the xml document
"""
with open(filename, 'rb') as import_file:
# NOTE: We don't need to do any of the normal encoding detection here, because lxml does it's own encoding
# detection, and the two mechanisms together interfere with each other.
if not use_objectify:
tree = etree.parse(import_file, parser=etree.XMLParser(recover=True))
else:
tree = objectify.parse(import_file, parser=objectify.makeparser(recover=True))
if elements:
# Strip tags we don't use - remove content
etree.strip_elements(tree, elements, with_tail=False)
if tags:
# Strip tags we don't use - keep content
etree.strip_tags(tree, tags)
return tree.getroot()

View File

@ -1,173 +0,0 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2016 OpenLP Developers #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
The :mod:`cvsbible` modules provides a facility to import bibles from a set of CSV files.
The module expects two mandatory files containing the books and the verses.
The format of the books file is:
<book_id>,<testament_id>,<book_name>,<book_abbreviation>
For example
1,1,Genesis,Gen
2,1,Exodus,Exod
...
40,2,Matthew,Matt
There are two acceptable formats of the verses file. They are:
<book_id>,<chapter_number>,<verse_number>,<verse_text>
or
<book_name>,<chapter_number>,<verse_number>,<verse_text>
For example:
1,1,1,"In the beginning God created the heaven and the earth."
or
"Genesis",1,2,"And the earth was without form, and void; and...."
All CSV files are expected to use a comma (',') as the delimiter and double quotes ('"') as the quote symbol.
"""
import logging
import chardet
import csv
from openlp.core.common import translate
from openlp.plugins.bibles.lib.db import BibleDB, BiblesResourcesDB
log = logging.getLogger(__name__)
class CSVBible(BibleDB):
"""
This class provides a specialisation for importing of CSV Bibles.
"""
log.info('CSVBible loaded')
def __init__(self, parent, **kwargs):
"""
Loads a Bible from a set of CSV files. This class assumes the files contain all the information and a clean
bible is being loaded.
"""
log.info(self.__class__.__name__)
BibleDB.__init__(self, parent, **kwargs)
self.books_file = kwargs['booksfile']
self.verses_file = kwargs['versefile']
def do_import(self, bible_name=None):
"""
Import the bible books and verses.
"""
self.wizard.progress_bar.setValue(0)
self.wizard.progress_bar.setMinimum(0)
self.wizard.progress_bar.setMaximum(66)
success = True
language_id = self.get_language(bible_name)
if not language_id:
log.error('Importing books from "{name}" failed'.format(name=self.filename))
return False
books_file = None
book_list = {}
# Populate the Tables
try:
details = get_file_encoding(self.books_file)
books_file = open(self.books_file, 'r', encoding=details['encoding'])
books_reader = csv.reader(books_file, delimiter=',', quotechar='"')
for line in books_reader:
if self.stop_import_flag:
break
self.wizard.increment_progress_bar(translate('BiblesPlugin.CSVBible',
'Importing books... {text}').format(text=line[2]))
book_ref_id = self.get_book_ref_id_by_name(line[2], 67, language_id)
if not book_ref_id:
log.error('Importing books from "{name}" failed'.format(name=self.books_file))
return False
book_details = BiblesResourcesDB.get_book_by_id(book_ref_id)
self.create_book(line[2], book_ref_id, book_details['testament_id'])
book_list.update({int(line[0]): line[2]})
self.application.process_events()
except (IOError, IndexError):
log.exception('Loading books from file failed')
success = False
finally:
if books_file:
books_file.close()
if self.stop_import_flag or not success:
return False
self.wizard.progress_bar.setValue(0)
self.wizard.progress_bar.setMaximum(67)
verse_file = None
try:
book_ptr = None
details = get_file_encoding(self.verses_file)
verse_file = open(self.verses_file, 'r', encoding=details['encoding'])
verse_reader = csv.reader(verse_file, delimiter=',', quotechar='"')
for line in verse_reader:
if self.stop_import_flag:
break
try:
line_book = book_list[int(line[0])]
except ValueError:
line_book = line[0]
if book_ptr != line_book:
book = self.get_book(line_book)
book_ptr = book.name
# TODO: Check out this conversion in translations
self.wizard.increment_progress_bar(
translate('BiblesPlugin.CSVBible',
'Importing verses from {name}...'.format(name=book.name),
'Importing verses from <book name>...'))
self.session.commit()
verse_text = line[3]
self.create_verse(book.id, line[1], line[2], verse_text)
self.wizard.increment_progress_bar(translate('BiblesPlugin.CSVBible', 'Importing verses... done.'))
self.application.process_events()
self.session.commit()
except IOError:
log.exception('Loading verses from file failed')
success = False
finally:
if verse_file:
verse_file.close()
if self.stop_import_flag:
return False
else:
return success
def get_file_encoding(filename):
"""
Utility function to get the file encoding.
"""
detect_file = None
try:
detect_file = open(filename, 'rb')
details = chardet.detect(detect_file.read(1024))
except IOError:
log.exception('Error detecting file encoding')
finally:
if detect_file:
detect_file.close()
return details

View File

@ -477,7 +477,7 @@ class BibleDB(Manager, RegistryProperties):
combo_box = language_form.language_combo_box
language_id = combo_box.itemData(combo_box.currentIndex())
if not language_id:
return False
return None
self.save_meta('language_id', language_id)
return language_id
@ -864,138 +864,3 @@ class AlternativeBookNamesDB(QtCore.QObject, Manager):
return AlternativeBookNamesDB.run_sql(
'INSERT INTO alternative_book_names(book_reference_id, language_id, name) '
'VALUES (?, ?, ?)', (book_reference_id, language_id, name), True)
class OldBibleDB(QtCore.QObject, Manager):
"""
This class connects to the old bible databases to reimport them to the new
database scheme.
"""
cursor = None
def __init__(self, parent, **kwargs):
"""
The constructor loads up the database and creates and initialises the tables if the database doesn't exist.
**Required keyword arguments:**
``path``
The path to the bible database file.
``name``
The name of the database. This is also used as the file name for SQLite databases.
"""
log.info('OldBibleDB loaded')
QtCore.QObject.__init__(self)
if 'path' not in kwargs:
raise KeyError('Missing keyword argument "path".')
if 'file' not in kwargs:
raise KeyError('Missing keyword argument "file".')
if 'path' in kwargs:
self.path = kwargs['path']
if 'file' in kwargs:
self.file = kwargs['file']
def get_cursor(self):
"""
Return the cursor object. Instantiate one if it doesn't exist yet.
"""
if self.cursor is None:
file_path = os.path.join(self.path, self.file)
self.connection = sqlite3.connect(file_path)
self.cursor = self.connection.cursor()
return self.cursor
def run_sql(self, query, parameters=()):
"""
Run an SQL query on the database, returning the results.
:param query: The actual SQL query to run.
:param parameters: Any variable parameters to add to the query.
"""
cursor = self.get_cursor()
cursor.execute(query, parameters)
return cursor.fetchall()
def get_name(self):
"""
Returns the version name of the Bible.
"""
self.name = None
version_name = self.run_sql('SELECT value FROM metadata WHERE key = "name"')
if version_name:
self.name = version_name[0][0]
else:
# Fallback to old way of naming
version_name = self.run_sql('SELECT value FROM metadata WHERE key = "Version"')
if version_name:
self.name = version_name[0][0]
return self.name
def get_metadata(self):
"""
Returns the metadata of the Bible.
"""
metadata = self.run_sql('SELECT key, value FROM metadata ORDER BY rowid')
if metadata:
return [{
'key': str(meta[0]),
'value': str(meta[1])
} for meta in metadata]
else:
return None
def get_book(self, name):
"""
Return a book by name or abbreviation.
``name``
The name or abbreviation of the book.
"""
if not isinstance(name, str):
name = str(name)
books = self.run_sql(
'SELECT id, testament_id, name, abbreviation FROM book WHERE LOWER(name) = ? OR '
'LOWER(abbreviation) = ?', (name.lower(), name.lower()))
if books:
return {
'id': books[0][0],
'testament_id': books[0][1],
'name': str(books[0][2]),
'abbreviation': str(books[0][3])
}
else:
return None
def get_books(self):
"""
Returns the books of the Bible.
"""
books = self.run_sql('SELECT name, id FROM book ORDER BY id')
if books:
return [{
'name': str(book[0]),
'id':int(book[1])
} for book in books]
else:
return None
def get_verses(self, book_id):
"""
Returns the verses of the Bible.
"""
verses = self.run_sql(
'SELECT book_id, chapter, verse, text FROM verse WHERE book_id = ? ORDER BY id', (book_id, ))
if verses:
return [{
'book_id': int(verse[0]),
'chapter': int(verse[1]),
'verse': int(verse[2]),
'text': str(verse[3])
} for verse in verses]
else:
return None
def close_connection(self):
self.cursor.close()
self.connection.close()

View File

@ -0,0 +1,186 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2016 OpenLP Developers #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
The :mod:`cvsbible` modules provides a facility to import bibles from a set of CSV files.
The module expects two mandatory files containing the books and the verses.
The format of the books file is:
<book_id>,<testament_id>,<book_name>,<book_abbreviation>
For example
1,1,Genesis,Gen
2,1,Exodus,Exod
...
40,2,Matthew,Matt
There are two acceptable formats of the verses file. They are:
<book_id>,<chapter_number>,<verse_number>,<verse_text>
or
<book_name>,<chapter_number>,<verse_number>,<verse_text>
For example:
1,1,1,"In the beginning God created the heaven and the earth."
or
"Genesis",1,2,"And the earth was without form, and void; and...."
All CSV files are expected to use a comma (',') as the delimiter and double quotes ('"') as the quote symbol.
"""
import csv
import logging
from collections import namedtuple
from openlp.core.common import get_file_encoding, translate
from openlp.core.lib.exceptions import ValidationError
from openlp.plugins.bibles.lib.bibleimport import BibleImport
log = logging.getLogger(__name__)
Book = namedtuple('Book', 'id, testament_id, name, abbreviation')
Verse = namedtuple('Verse', 'book_id_name, chapter_number, number, text')
class CSVBible(BibleImport):
"""
This class provides a specialisation for importing of CSV Bibles.
"""
log.info('CSVBible loaded')
def __init__(self, *args, **kwargs):
"""
Loads a Bible from a set of CSV files. This class assumes the files contain all the information and a clean
bible is being loaded.
"""
log.info(self.__class__.__name__)
super().__init__(*args, **kwargs)
self.books_file = kwargs['booksfile']
self.verses_file = kwargs['versefile']
@staticmethod
def get_book_name(name, books):
"""
Normalize a book name or id.
:param name: The name, or id of a book. Str
:param books: A dict of books parsed from the books file.
:return: The normalized name. Str
"""
try:
book_name = books[int(name)]
except ValueError:
book_name = name
return book_name
@staticmethod
def parse_csv_file(filename, results_tuple):
"""
Parse the supplied CSV file.
:param filename: The name of the file to parse. Str
:param results_tuple: The namedtuple to use to store the results. namedtuple
:return: An iterable yielding namedtuples of type results_tuple
"""
try:
encoding = get_file_encoding(filename)['encoding']
with open(filename, 'r', encoding=encoding, newline='') as csv_file:
csv_reader = csv.reader(csv_file, delimiter=',', quotechar='"')
return [results_tuple(*line) for line in csv_reader]
except (OSError, csv.Error):
raise ValidationError(msg='Parsing "{file}" failed'.format(file=filename))
def process_books(self, books):
"""
Process the books parsed from the books file.
:param books: An a list Book namedtuples
:return: A dict of books or None
"""
book_list = {}
number_of_books = len(books)
for book in books:
if self.stop_import_flag:
return None
self.wizard.increment_progress_bar(
translate('BiblesPlugin.CSVBible', 'Importing books... {book}').format(book=book.name))
self.find_and_create_book(book.name, number_of_books, self.language_id)
book_list.update({int(book.id): book.name})
self.application.process_events()
return book_list
def process_verses(self, verses, books):
"""
Process the verses parsed from the verses file.
:param verses: A list of Verse namedtuples
:param books: A dict of books
:return: None
"""
book_ptr = None
for verse in verses:
if self.stop_import_flag:
return None
verse_book = self.get_book_name(verse.book_id_name, books)
if book_ptr != verse_book:
book = self.get_book(verse_book)
book_ptr = book.name
self.wizard.increment_progress_bar(
translate('BiblesPlugin.CSVBible', 'Importing verses from {book}...',
'Importing verses from <book name>...').format(book=book.name))
self.session.commit()
self.create_verse(book.id, verse.chapter_number, verse.number, verse.text)
self.wizard.increment_progress_bar(translate('BiblesPlugin.CSVBible', 'Importing verses... done.'))
self.application.process_events()
self.session.commit()
def do_import(self, bible_name=None):
"""
Import a bible from the CSV files.
:param bible_name: Optional name of the bible being imported. Str or None
:return: True if the import was successful, False if it failed or was cancelled
"""
try:
self.language_id = self.get_language(bible_name)
if not self.language_id:
raise ValidationError(msg='Invalid language selected')
books = self.parse_csv_file(self.books_file, Book)
self.wizard.progress_bar.setValue(0)
self.wizard.progress_bar.setMinimum(0)
self.wizard.progress_bar.setMaximum(len(books))
book_list = self.process_books(books)
if self.stop_import_flag:
return False
verses = self.parse_csv_file(self.verses_file, Verse)
self.wizard.progress_bar.setValue(0)
self.wizard.progress_bar.setMaximum(len(books) + 1)
self.process_verses(verses, book_list)
if self.stop_import_flag:
return False
except ValidationError:
log.exception('Could not import CSV bible')
return False
return True

View File

@ -34,6 +34,7 @@ from openlp.core.common import Registry, RegistryProperties, translate
from openlp.core.lib.ui import critical_error_message_box
from openlp.core.lib.webpagereader import get_web_page
from openlp.plugins.bibles.lib import SearchResults
from openlp.plugins.bibles.lib.bibleimport import BibleImport
from openlp.plugins.bibles.lib.db import BibleDB, BiblesResourcesDB, Book
CLEANER_REGEX = re.compile(r'&nbsp;|<br />|\'\+\'')
@ -576,10 +577,10 @@ class CWExtract(RegistryProperties):
return bibles
class HTTPBible(BibleDB, RegistryProperties):
class HTTPBible(BibleImport, RegistryProperties):
log.info('{name} HTTPBible loaded'.format(name=__name__))
def __init__(self, parent, **kwargs):
def __init__(self, *args, **kwargs):
"""
Finds all the bibles defined for the system. Creates an Interface Object for each bible containing connection
information.
@ -588,7 +589,7 @@ class HTTPBible(BibleDB, RegistryProperties):
Init confirms the bible exists and stores the database path.
"""
BibleDB.__init__(self, parent, **kwargs)
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.
@ -638,12 +639,8 @@ class HTTPBible(BibleDB, RegistryProperties):
return False
self.wizard.progress_bar.setMaximum(len(books) + 2)
self.wizard.increment_progress_bar(translate('BiblesPlugin.HTTPBible', 'Registering Language...'))
if self.language_id:
self.save_meta('language_id', self.language_id)
else:
self.language_id = self.get_language(bible_name)
self.language_id = self.get_language_id(bible_name=bible_name)
if not self.language_id:
log.error('Importing books from {name} failed'.format(name=self.filename))
return False
for book in books:
if self.stop_import_flag:

View File

@ -25,25 +25,17 @@ from lxml import etree, objectify
from openlp.core.common import translate, trace_error_handler
from openlp.core.lib.ui import critical_error_message_box
from openlp.plugins.bibles.lib.bibleimport import BibleImport
from openlp.plugins.bibles.lib.db import BibleDB, BiblesResourcesDB
log = logging.getLogger(__name__)
class OpenSongBible(BibleDB):
class OpenSongBible(BibleImport):
"""
OpenSong Bible format importer class.
OpenSong Bible format importer class. This class is used to import Bibles from OpenSong's XML format.
"""
def __init__(self, parent, **kwargs):
"""
Constructor to create and set up an instance of the OpenSongBible class. This class is used to import Bibles
from OpenSong's XML format.
"""
log.debug(self.__class__.__name__)
BibleDB.__init__(self, parent, **kwargs)
self.filename = kwargs['filename']
def get_text(self, element):
"""
Recursively get all text in an objectify element and its child elements.
@ -64,16 +56,9 @@ class OpenSongBible(BibleDB):
Loads a Bible from file.
"""
log.debug('Starting OpenSong import from "{name}"'.format(name=self.filename))
if not isinstance(self.filename, str):
self.filename = str(self.filename, 'utf8')
import_file = None
success = True
try:
# NOTE: We don't need to do any of the normal encoding detection here, because lxml does it's own encoding
# detection, and the two mechanisms together interfere with each other.
import_file = open(self.filename, 'rb')
opensong = objectify.parse(import_file)
bible = opensong.getroot()
bible = self.parse_xml(self.filename, use_objectify=True)
# Check that we're not trying to import a Zefania XML bible, it is sometimes refered to as 'OpenSong'
if bible.tag.upper() == 'XMLBIBLE':
critical_error_message_box(
@ -82,9 +67,8 @@ class OpenSongBible(BibleDB):
'please use the Zefania import option.'))
return False
# No language info in the opensong format, so ask the user
language_id = self.get_language(bible_name)
language_id = self.get_language_id(bible_name=self.filename)
if not language_id:
log.error('Importing books from "{name}" failed'.format(name=self.filename))
return False
for book in bible.b:
if self.stop_import_flag:
@ -138,9 +122,6 @@ class OpenSongBible(BibleDB):
except (IOError, AttributeError):
log.exception('Loading Bible from OpenSong file failed')
success = False
finally:
if import_file:
import_file.close()
if self.stop_import_flag:
return False
else:

View File

@ -23,105 +23,87 @@
import logging
from lxml import etree
from openlp.core.common import languages, translate, trace_error_handler
from openlp.plugins.bibles.lib.db import BibleDB, BiblesResourcesDB
from openlp.core.common import translate, trace_error_handler
from openlp.core.lib.ui import critical_error_message_box
from openlp.plugins.bibles.lib.bibleimport import BibleImport
from openlp.plugins.bibles.lib.db import BiblesResourcesDB
log = logging.getLogger(__name__)
NS = {'ns': 'http://www.bibletechnologies.net/2003/OSIS/namespace'}
# Tags we don't use and can remove the content
REMOVABLE_ELEMENTS = (
'{http://www.bibletechnologies.net/2003/OSIS/namespace}note',
'{http://www.bibletechnologies.net/2003/OSIS/namespace}milestone',
'{http://www.bibletechnologies.net/2003/OSIS/namespace}title',
'{http://www.bibletechnologies.net/2003/OSIS/namespace}abbr',
'{http://www.bibletechnologies.net/2003/OSIS/namespace}catchWord',
'{http://www.bibletechnologies.net/2003/OSIS/namespace}index',
'{http://www.bibletechnologies.net/2003/OSIS/namespace}rdg',
'{http://www.bibletechnologies.net/2003/OSIS/namespace}rdgGroup',
'{http://www.bibletechnologies.net/2003/OSIS/namespace}figure'
)
# Tags we don't use but need to keep the content
REMOVABLE_TAGS = (
'{http://www.bibletechnologies.net/2003/OSIS/namespace}p',
'{http://www.bibletechnologies.net/2003/OSIS/namespace}l',
'{http://www.bibletechnologies.net/2003/OSIS/namespace}lg',
'{http://www.bibletechnologies.net/2003/OSIS/namespace}q',
'{http://www.bibletechnologies.net/2003/OSIS/namespace}a',
'{http://www.bibletechnologies.net/2003/OSIS/namespace}w',
'{http://www.bibletechnologies.net/2003/OSIS/namespace}divineName',
'{http://www.bibletechnologies.net/2003/OSIS/namespace}foreign',
'{http://www.bibletechnologies.net/2003/OSIS/namespace}hi',
'{http://www.bibletechnologies.net/2003/OSIS/namespace}inscription',
'{http://www.bibletechnologies.net/2003/OSIS/namespace}mentioned',
'{http://www.bibletechnologies.net/2003/OSIS/namespace}name',
'{http://www.bibletechnologies.net/2003/OSIS/namespace}reference',
'{http://www.bibletechnologies.net/2003/OSIS/namespace}seg',
'{http://www.bibletechnologies.net/2003/OSIS/namespace}transChange',
'{http://www.bibletechnologies.net/2003/OSIS/namespace}salute',
'{http://www.bibletechnologies.net/2003/OSIS/namespace}signed',
'{http://www.bibletechnologies.net/2003/OSIS/namespace}closer',
'{http://www.bibletechnologies.net/2003/OSIS/namespace}speech',
'{http://www.bibletechnologies.net/2003/OSIS/namespace}speaker',
'{http://www.bibletechnologies.net/2003/OSIS/namespace}list',
'{http://www.bibletechnologies.net/2003/OSIS/namespace}item',
'{http://www.bibletechnologies.net/2003/OSIS/namespace}table',
'{http://www.bibletechnologies.net/2003/OSIS/namespace}head',
'{http://www.bibletechnologies.net/2003/OSIS/namespace}row',
'{http://www.bibletechnologies.net/2003/OSIS/namespace}cell',
'{http://www.bibletechnologies.net/2003/OSIS/namespace}caption'
)
def replacement(match):
return match.group(2).upper()
class OSISBible(BibleDB):
class OSISBible(BibleImport):
"""
`OSIS <http://www.bibletechnologies.net/>`_ Bible format importer class.
"""
log.info('BibleOSISImpl loaded')
def __init__(self, parent, **kwargs):
log.debug(self.__class__.__name__)
BibleDB.__init__(self, parent, **kwargs)
self.filename = kwargs['filename']
def do_import(self, bible_name=None):
"""
Loads a Bible from file.
"""
log.debug('Starting OSIS import from "{name}"'.format(name=self.filename))
if not isinstance(self.filename, str):
self.filename = str(self.filename, 'utf8')
import_file = None
success = True
try:
# NOTE: We don't need to do any of the normal encoding detection here, because lxml does it's own encoding
# detection, and the two mechanisms together interfere with each other.
import_file = open(self.filename, 'rb')
osis_bible_tree = etree.parse(import_file, parser=etree.XMLParser(recover=True))
namespace = {'ns': 'http://www.bibletechnologies.net/2003/OSIS/namespace'}
# Find bible language
language_id = None
lang = osis_bible_tree.xpath("//ns:osisText/@xml:lang", namespaces=namespace)
if lang:
language = languages.get_language(lang[0])
if hasattr(language, 'id'):
language_id = language.id
# The language couldn't be detected, ask the user
if not language_id:
language_id = self.get_language(bible_name)
if not language_id:
log.error('Importing books from "{name}" failed'.format(name=self.filename))
return False
self.save_meta('language_id', language_id)
num_books = int(osis_bible_tree.xpath("count(//ns:div[@type='book'])", namespaces=namespace))
self.wizard.increment_progress_bar(translate('BiblesPlugin.OsisImport',
'Removing unused tags (this may take a few minutes)...'))
# We strip unused tags from the XML, this should leave us with only chapter, verse and div tags.
# Strip tags we don't use - remove content
etree.strip_elements(osis_bible_tree, ('{http://www.bibletechnologies.net/2003/OSIS/namespace}note',
'{http://www.bibletechnologies.net/2003/OSIS/namespace}milestone',
'{http://www.bibletechnologies.net/2003/OSIS/namespace}title',
'{http://www.bibletechnologies.net/2003/OSIS/namespace}abbr',
'{http://www.bibletechnologies.net/2003/OSIS/namespace}catchWord',
'{http://www.bibletechnologies.net/2003/OSIS/namespace}index',
'{http://www.bibletechnologies.net/2003/OSIS/namespace}rdg',
'{http://www.bibletechnologies.net/2003/OSIS/namespace}rdgGroup',
'{http://www.bibletechnologies.net/2003/OSIS/namespace}figure'),
with_tail=False)
# Strip tags we don't use - keep content
etree.strip_tags(osis_bible_tree, ('{http://www.bibletechnologies.net/2003/OSIS/namespace}p',
'{http://www.bibletechnologies.net/2003/OSIS/namespace}l',
'{http://www.bibletechnologies.net/2003/OSIS/namespace}lg',
'{http://www.bibletechnologies.net/2003/OSIS/namespace}q',
'{http://www.bibletechnologies.net/2003/OSIS/namespace}a',
'{http://www.bibletechnologies.net/2003/OSIS/namespace}w',
'{http://www.bibletechnologies.net/2003/OSIS/namespace}divineName',
'{http://www.bibletechnologies.net/2003/OSIS/namespace}foreign',
'{http://www.bibletechnologies.net/2003/OSIS/namespace}hi',
'{http://www.bibletechnologies.net/2003/OSIS/namespace}inscription',
'{http://www.bibletechnologies.net/2003/OSIS/namespace}mentioned',
'{http://www.bibletechnologies.net/2003/OSIS/namespace}name',
'{http://www.bibletechnologies.net/2003/OSIS/namespace}reference',
'{http://www.bibletechnologies.net/2003/OSIS/namespace}seg',
'{http://www.bibletechnologies.net/2003/OSIS/namespace}transChange',
'{http://www.bibletechnologies.net/2003/OSIS/namespace}salute',
'{http://www.bibletechnologies.net/2003/OSIS/namespace}signed',
'{http://www.bibletechnologies.net/2003/OSIS/namespace}closer',
'{http://www.bibletechnologies.net/2003/OSIS/namespace}speech',
'{http://www.bibletechnologies.net/2003/OSIS/namespace}speaker',
'{http://www.bibletechnologies.net/2003/OSIS/namespace}list',
'{http://www.bibletechnologies.net/2003/OSIS/namespace}item',
'{http://www.bibletechnologies.net/2003/OSIS/namespace}table',
'{http://www.bibletechnologies.net/2003/OSIS/namespace}head',
'{http://www.bibletechnologies.net/2003/OSIS/namespace}row',
'{http://www.bibletechnologies.net/2003/OSIS/namespace}cell',
'{http://www.bibletechnologies.net/2003/OSIS/namespace}caption'))
osis_bible_tree = self.parse_xml(self.filename, elements=REMOVABLE_ELEMENTS, tags=REMOVABLE_TAGS)
# Find bible language]
language = osis_bible_tree.xpath("//ns:osisText/@xml:lang", namespaces=NS)
language_id = self.get_language_id(language[0] if language else None, bible_name=self.filename)
if not language_id:
return False
num_books = int(osis_bible_tree.xpath("count(//ns:div[@type='book'])", namespaces=NS))
# Precompile a few xpath-querys
verse_in_chapter = etree.XPath('count(//ns:chapter[1]/ns:verse)', namespaces=namespace)
text_in_verse = etree.XPath('count(//ns:verse[1]/text())', namespaces=namespace)
verse_in_chapter = etree.XPath('count(//ns:chapter[1]/ns:verse)', namespaces=NS)
text_in_verse = etree.XPath('count(//ns:verse[1]/text())', namespaces=NS)
# Find books in the bible
bible_books = osis_bible_tree.xpath("//ns:div[@type='book']", namespaces=namespace)
bible_books = osis_bible_tree.xpath("//ns:div[@type='book']", namespaces=NS)
for book in bible_books:
if self.stop_import_flag:
break
@ -189,9 +171,6 @@ class OSISBible(BibleDB):
critical_error_message_box(message=translate('BiblesPlugin.OsisImport',
'The file is not a valid OSIS-XML file:'
'\n{text}').format(text=e.msg))
finally:
if import_file:
import_file.close()
if self.stop_import_flag:
return False
else:

View File

@ -23,25 +23,26 @@
import logging
from pysword import modules
from openlp.core.common import languages, translate
from openlp.core.common import translate
from openlp.core.lib.ui import critical_error_message_box
from openlp.plugins.bibles.lib.db import BibleDB, BiblesResourcesDB
from openlp.plugins.bibles.lib.bibleimport import BibleImport
from openlp.plugins.bibles.lib.db import BiblesResourcesDB
log = logging.getLogger(__name__)
class SwordBible(BibleDB):
class SwordBible(BibleImport):
"""
SWORD Bible format importer class.
"""
def __init__(self, parent, **kwargs):
def __init__(self, *args, **kwargs):
"""
Constructor to create and set up an instance of the SwordBible class. This class is used to import Bibles
from SWORD bible modules.
"""
log.debug(self.__class__.__name__)
BibleDB.__init__(self, parent, **kwargs)
super().__init__(*args, **kwargs)
self.sword_key = kwargs['sword_key']
self.sword_path = kwargs['sword_path']
if self.sword_path == '':
@ -57,13 +58,11 @@ class SwordBible(BibleDB):
pysword_modules = modules.SwordModules(self.sword_path)
pysword_module_json = pysword_modules.parse_modules()[self.sword_key]
bible = pysword_modules.get_bible_from_module(self.sword_key)
language_id = None
language = pysword_module_json['lang']
language = language[language.find('.') + 1:]
language = languages.get_language(language)
if hasattr(language, 'id'):
language_id = language.id
self.save_meta('language_id', language_id)
language_id = self.get_language_id(language, bible_name=self.filename)
if not language_id:
return False
books = bible.get_structure().get_books()
# Count number of books
num_books = 0

View File

@ -21,64 +21,41 @@
###############################################################################
import logging
from lxml import etree
from openlp.core.common import languages, translate
from openlp.core.common import translate
from openlp.core.lib.ui import critical_error_message_box
from openlp.plugins.bibles.lib.db import BibleDB, BiblesResourcesDB
from openlp.plugins.bibles.lib.bibleimport import BibleImport
from openlp.plugins.bibles.lib.db import BiblesResourcesDB
log = logging.getLogger(__name__)
# Tags we don't use and can remove the content
REMOVABLE_ELEMENTS = ('PROLOG', 'REMARK', 'CAPTION', 'MEDIA')
# Tags we don't use but need to keep the content
REMOVABLE_TAGS = ('STYLE', 'GRAM', 'NOTE', 'SUP', 'XREF')
class ZefaniaBible(BibleDB):
class ZefaniaBible(BibleImport):
"""
Zefania Bible format importer class.
Zefania Bible format importer class. This class is used to import Bibles from ZefaniaBible's XML format.
"""
def __init__(self, parent, **kwargs):
"""
Constructor to create and set up an instance of the ZefaniaBible class. This class is used to import Bibles
from ZefaniaBible's XML format.
"""
log.debug(self.__class__.__name__)
BibleDB.__init__(self, parent, **kwargs)
self.filename = kwargs['filename']
def do_import(self, bible_name=None):
"""
Loads a Bible from file.
"""
log.debug('Starting Zefania import from "{name}"'.format(name=self.filename))
if not isinstance(self.filename, str):
self.filename = str(self.filename, 'utf8')
import_file = None
success = True
try:
# NOTE: We don't need to do any of the normal encoding detection here, because lxml does it's own encoding
# detection, and the two mechanisms together interfere with each other.
import_file = open(self.filename, 'rb')
zefania_bible_tree = etree.parse(import_file, parser=etree.XMLParser(recover=True))
xmlbible = self.parse_xml(self.filename, elements=REMOVABLE_ELEMENTS, tags=REMOVABLE_TAGS)
# Find bible language
language_id = None
language = zefania_bible_tree.xpath("/XMLBIBLE/INFORMATION/language/text()")
if language:
language = languages.get_language(language[0])
if hasattr(language, 'id'):
language_id = language.id
# The language couldn't be detected, ask the user
language = xmlbible.xpath("/XMLBIBLE/INFORMATION/language/text()")
language_id = self.get_language_id(language[0] if language else None, bible_name=self.filename)
if not language_id:
language_id = self.get_language(bible_name)
if not language_id:
log.error('Importing books from "{name}" failed'.format(name=self.filename))
return False
self.save_meta('language_id', language_id)
num_books = int(zefania_bible_tree.xpath('count(//BIBLEBOOK)'))
self.wizard.progress_bar.setMaximum(int(zefania_bible_tree.xpath('count(//CHAPTER)')))
# Strip tags we don't use - keep content
etree.strip_tags(zefania_bible_tree, ('STYLE', 'GRAM', 'NOTE', 'SUP', 'XREF'))
# Strip tags we don't use - remove content
etree.strip_elements(zefania_bible_tree, ('PROLOG', 'REMARK', 'CAPTION', 'MEDIA'), with_tail=False)
xmlbible = zefania_bible_tree.getroot()
num_books = int(xmlbible.xpath('count(//BIBLEBOOK)'))
self.wizard.progress_bar.setMaximum(int(xmlbible.xpath('count(//CHAPTER)')))
for BIBLEBOOK in xmlbible:
if self.stop_import_flag:
break
@ -116,9 +93,6 @@ class ZefaniaBible(BibleDB):
'compressed. You must decompress them before import.'))
log.exception(str(e))
success = False
finally:
if import_file:
import_file.close()
if self.stop_import_flag:
return False
else:

View File

@ -26,13 +26,13 @@ import os
from openlp.core.common import RegistryProperties, AppLocation, Settings, translate, delete_file, UiStrings
from openlp.plugins.bibles.lib import parse_reference, LanguageSelection
from openlp.plugins.bibles.lib.db import BibleDB, BibleMeta
from .csvbible import CSVBible
from .http import HTTPBible
from .opensong import OpenSongBible
from .osis import OSISBible
from .zefania import ZefaniaBible
from .importers.csvbible import CSVBible
from .importers.http import HTTPBible
from .importers.opensong import OpenSongBible
from .importers.osis import OSISBible
from .importers.zefania import ZefaniaBible
try:
from .sword import SwordBible
from .importers.sword import SwordBible
except:
pass
@ -124,7 +124,6 @@ class BibleManager(RegistryProperties):
files.remove('alternative_book_names.sqlite')
log.debug('Bible Files {text}'.format(text=files))
self.db_cache = {}
self.old_bible_databases = []
for filename in files:
bible = BibleDB(self.parent, path=self.path, file=filename)
if not bible.session:

View File

@ -23,11 +23,12 @@
Functional tests to test the AppLocation class and related methods.
"""
import os
from io import BytesIO
from unittest import TestCase
from openlp.core.common import add_actions, get_uno_instance, get_uno_command, delete_file, get_filesystem_encoding, \
split_filename, clean_filename
from tests.functional import MagicMock, patch
from openlp.core.common import add_actions, clean_filename, delete_file, get_file_encoding, get_filesystem_encoding, \
get_uno_command, get_uno_instance, split_filename
from tests.functional import MagicMock, PropertyMock, call, patch
from tests.helpers.testmixin import TestMixin
@ -340,3 +341,63 @@ class TestInit(TestCase, TestMixin):
# THEN: delete_file should log and exception and return False
self.assertEqual(mocked_log.exception.call_count, 1)
self.assertFalse(result, 'delete_file should return False when os.remove raises an OSError')
def test_get_file_name_encoding_done_test(self):
"""
Test get_file_encoding when the detector sets done to True
"""
# GIVEN: A mocked UniversalDetector instance with done attribute set to True after first iteration
with patch('openlp.core.common.UniversalDetector') as mocked_universal_detector, \
patch('builtins.open', return_value=BytesIO(b"data" * 260)) as mocked_open:
encoding_result = {'encoding': 'UTF-8', 'confidence': 0.99}
mocked_universal_detector_inst = MagicMock(result=encoding_result)
type(mocked_universal_detector_inst).done = PropertyMock(side_effect=[False, True])
mocked_universal_detector.return_value = mocked_universal_detector_inst
# WHEN: Calling get_file_encoding
result = get_file_encoding('file name')
# THEN: The feed method of UniversalDetector should only br called once before returning a result
mocked_open.assert_called_once_with('file name', 'rb')
self.assertEqual(mocked_universal_detector_inst.feed.mock_calls, [call(b"data" * 256)])
mocked_universal_detector_inst.close.assert_called_once_with()
self.assertEqual(result, encoding_result)
def test_get_file_name_encoding_eof_test(self):
"""
Test get_file_encoding when the end of the file is reached
"""
# GIVEN: A mocked UniversalDetector instance which isn't set to done and a mocked open, with 1040 bytes of test
# data (enough to run the iterator twice)
with patch('openlp.core.common.UniversalDetector') as mocked_universal_detector, \
patch('builtins.open', return_value=BytesIO(b"data" * 260)) as mocked_open:
encoding_result = {'encoding': 'UTF-8', 'confidence': 0.99}
mocked_universal_detector_inst = MagicMock(mock=mocked_universal_detector,
**{'done': False, 'result': encoding_result})
mocked_universal_detector.return_value = mocked_universal_detector_inst
# WHEN: Calling get_file_encoding
result = get_file_encoding('file name')
# THEN: The feed method of UniversalDetector should have been called twice before returning a result
mocked_open.assert_called_once_with('file name', 'rb')
self.assertEqual(mocked_universal_detector_inst.feed.mock_calls, [call(b"data" * 256), call(b"data" * 4)])
mocked_universal_detector_inst.close.assert_called_once_with()
self.assertEqual(result, encoding_result)
def test_get_file_name_encoding_oserror_test(self):
"""
Test get_file_encoding when the end of the file is reached
"""
# GIVEN: A mocked UniversalDetector instance which isn't set to done and a mocked open, with 1040 bytes of test
# data (enough to run the iterator twice)
with patch('openlp.core.common.UniversalDetector'), \
patch('builtins.open', side_effect=OSError), \
patch('openlp.core.common.log') as mocked_log:
# WHEN: Calling get_file_encoding
result = get_file_encoding('file name')
# THEN: log.exception should be called and get_file_encoding should return None
mocked_log.exception.assert_called_once_with('Error detecting file encoding')
self.assertIsNone(result)

View File

@ -75,3 +75,24 @@ class TestRegistryProperties(TestCase, RegistryProperties):
# THEN the application should be none
self.assertEqual(self.application, application, 'The application value should match')
@patch('openlp.core.common.registryproperties.is_win')
def test_get_application_on_windows(self, mocked_is_win):
"""
Set that getting the application object on Windows happens dynamically
"""
# GIVEN an Empty Registry and we're on Windows
mocked_is_win.return_value = True
mock_application = MagicMock()
reg_props = RegistryProperties()
registry = Registry()
# WHEN the application is accessed
with patch.object(registry, 'get') as mocked_get:
mocked_get.return_value = mock_application
actual_application = reg_props.application
# THEN the application should be the mock object, and the correct function should have been called
self.assertEqual(mock_application, actual_application, 'The application value should match')
mocked_is_win.assert_called_with()
mocked_get.assert_called_with('application')

View File

@ -0,0 +1,212 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2015 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 bibleimport module.
"""
from io import BytesIO
from lxml import etree, objectify
from unittest import TestCase
from openlp.core.common.languages import Language
from openlp.plugins.bibles.lib.bibleimport import BibleImport
from tests.functional import MagicMock, patch
class TestBibleImport(TestCase):
"""
Test the functions in the :mod:`bibleimport` module.
"""
def setUp(self):
test_file = BytesIO(b'<?xml version="1.0" encoding="UTF-8" ?>\n'
b'<root>\n'
b' <data><div>Test<p>data</p><a>to</a>keep</div></data>\n'
b' <data><unsupported>Test<x>data</x><y>to</y>discard</unsupported></data>\n'
b'</root>')
self.file_patcher = patch('builtins.open', return_value=test_file)
self.log_patcher = patch('openlp.plugins.bibles.lib.bibleimport.log')
self.setup_patcher = patch('openlp.plugins.bibles.lib.db.BibleDB._setup')
self.addCleanup(self.file_patcher.stop)
self.addCleanup(self.log_patcher.stop)
self.addCleanup(self.setup_patcher.stop)
self.file_patcher.start()
self.mock_log = self.log_patcher.start()
self.setup_patcher.start()
def get_language_id_language_found_test(self):
"""
Test get_language_id() when called with a name found in the languages list
"""
# GIVEN: A mocked languages.get_language which returns language and an instance of BibleImport
with patch('openlp.core.common.languages.get_language', return_value=Language(30, 'English', 'en')) \
as mocked_languages_get_language, \
patch('openlp.plugins.bibles.lib.db.BibleDB.get_language') as mocked_db_get_language:
instance = BibleImport(MagicMock())
instance.save_meta = MagicMock()
# WHEN: Calling get_language_id() with a language name and bible name
result = instance.get_language_id('English', 'KJV')
# THEN: The id of the language returned from languages.get_language should be returned
mocked_languages_get_language.assert_called_once_with('English')
self.assertFalse(mocked_db_get_language.called)
instance.save_meta.assert_called_once_with('language_id', 30)
self.assertEqual(result, 30)
def get_language_id_language_not_found_test(self):
"""
Test get_language_id() when called with a name not found in the languages list
"""
# GIVEN: A mocked languages.get_language which returns language and an instance of BibleImport
with patch('openlp.core.common.languages.get_language', return_value=None) \
as mocked_languages_get_language, \
patch('openlp.plugins.bibles.lib.db.BibleDB.get_language', return_value=20) as mocked_db_get_language:
instance = BibleImport(MagicMock())
instance.save_meta = MagicMock()
# WHEN: Calling get_language_id() with a language name and bible name
result = instance.get_language_id('RUS', 'KJV')
# THEN: The id of the language returned from languages.get_language should be returned
mocked_languages_get_language.assert_called_once_with('RUS')
mocked_db_get_language.assert_called_once_with('KJV')
instance.save_meta.assert_called_once_with('language_id', 20)
self.assertEqual(result, 20)
def get_language_id_user_choice_test(self):
"""
Test get_language_id() when the language is not found and the user is asked for the language
"""
# GIVEN: A mocked languages.get_language which returns None a mocked BibleDB.get_language which returns a
# language id.
with patch('openlp.core.common.languages.get_language', return_value=None) as mocked_languages_get_language, \
patch('openlp.plugins.bibles.lib.db.BibleDB.get_language', return_value=40) as mocked_db_get_language:
self.mock_log.error.reset_mock()
instance = BibleImport(MagicMock())
instance.save_meta = MagicMock()
# WHEN: Calling get_language_id() with a language name and bible name
result = instance.get_language_id('English', 'KJV')
# THEN: The id of the language returned from BibleDB.get_language should be returned
mocked_languages_get_language.assert_called_once_with('English')
mocked_db_get_language.assert_called_once_with('KJV')
self.assertFalse(self.mock_log.error.called)
instance.save_meta.assert_called_once_with('language_id', 40)
self.assertEqual(result, 40)
def get_language_id_user_choice_rejected_test(self):
"""
Test get_language_id() when the language is not found and the user rejects the dilaog box
"""
# GIVEN: A mocked languages.get_language which returns None a mocked BibleDB.get_language which returns a
# language id.
with patch('openlp.core.common.languages.get_language', return_value=None) as mocked_languages_get_language, \
patch('openlp.plugins.bibles.lib.db.BibleDB.get_language', return_value=None) as mocked_db_get_language:
self.mock_log.error.reset_mock()
instance = BibleImport(MagicMock())
instance.save_meta = MagicMock()
# WHEN: Calling get_language_id() with a language name and bible name
result = instance.get_language_id('Qwerty', 'KJV')
# THEN: None should be returned and an error should be logged
mocked_languages_get_language.assert_called_once_with('Qwerty')
mocked_db_get_language.assert_called_once_with('KJV')
self.mock_log.error.assert_called_once_with('Language detection failed when importing from "KJV". '
'User aborted language selection.')
self.assertFalse(instance.save_meta.called)
self.assertIsNone(result)
def parse_xml_etree_test(self):
"""
Test BibleImport.parse_xml() when called with the use_objectify default value
"""
# GIVEN: A sample "file" to parse
# WHEN: Calling parse_xml
result = BibleImport.parse_xml('file.tst')
# THEN: The result returned should contain the correct data, and should be an instance of eetree_Element
self.assertEqual(etree.tostring(result),
b'<root>\n <data><div>Test<p>data</p><a>to</a>keep</div></data>\n'
b' <data><unsupported>Test<x>data</x><y>to</y>discard</unsupported></data>\n</root>')
self.assertIsInstance(result, etree._Element)
def parse_xml_etree_use_objectify_test(self):
"""
Test BibleImport.parse_xml() when called with use_objectify set to True
"""
# GIVEN: A sample "file" to parse
# WHEN: Calling parse_xml
result = BibleImport.parse_xml('file.tst', use_objectify=True)
# THEN: The result returned should contain the correct data, and should be an instance of ObjectifiedElement
self.assertEqual(etree.tostring(result),
b'<root><data><div>Test<p>data</p><a>to</a>keep</div></data>'
b'<data><unsupported>Test<x>data</x><y>to</y>discard</unsupported></data></root>')
self.assertIsInstance(result, objectify.ObjectifiedElement)
def parse_xml_elements_test(self):
"""
Test BibleImport.parse_xml() when given a tuple of elements to remove
"""
# GIVEN: A tuple of elements to remove
elements = ('unsupported', 'x', 'y')
# WHEN: Calling parse_xml, with a test file
result = BibleImport.parse_xml('file.tst', elements=elements)
# THEN: The result returned should contain the correct data
self.assertEqual(etree.tostring(result),
b'<root>\n <data><div>Test<p>data</p><a>to</a>keep</div></data>\n <data/>\n</root>')
def parse_xml_tags_test(self):
"""
Test BibleImport.parse_xml() when given a tuple of tags to remove
"""
# GIVEN: A tuple of tags to remove
tags = ('div', 'p', 'a')
# WHEN: Calling parse_xml, with a test file
result = BibleImport.parse_xml('file.tst', tags=tags)
# THEN: The result returned should contain the correct data
self.assertEqual(etree.tostring(result), b'<root>\n <data>Testdatatokeep</data>\n <data><unsupported>Test'
b'<x>data</x><y>to</y>discard</unsupported></data>\n</root>')
def parse_xml_elements_tags_test(self):
"""
Test BibleImport.parse_xml() when given a tuple of elements and of tags to remove
"""
# GIVEN: A tuple of elements and of tags to remove
elements = ('unsupported', 'x', 'y')
tags = ('div', 'p', 'a')
# WHEN: Calling parse_xml, with a test file
result = BibleImport.parse_xml('file.tst', elements=elements, tags=tags)
# THEN: The result returned should contain the correct data
self.assertEqual(etree.tostring(result), b'<root>\n <data>Testdatatokeep</data>\n <data/>\n</root>')

View File

@ -23,13 +23,17 @@
This module contains tests for the CSV Bible importer.
"""
import os
import csv
import json
import os
from collections import namedtuple
from unittest import TestCase
from tests.functional import MagicMock, patch
from openlp.plugins.bibles.lib.csvbible import CSVBible
from openlp.plugins.bibles.lib.db import BibleDB
from tests.functional import ANY, MagicMock, PropertyMock, call, patch
from openlp.core.lib.exceptions import ValidationError
from openlp.plugins.bibles.lib.bibleimport import BibleImport
from openlp.plugins.bibles.lib.importers.csvbible import Book, CSVBible, Verse
TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__),
'..', '..', '..', 'resources', 'bibles'))
@ -41,14 +45,12 @@ class TestCSVImport(TestCase):
"""
def setUp(self):
self.registry_patcher = patch('openlp.plugins.bibles.lib.db.Registry')
self.registry_patcher.start()
self.manager_patcher = patch('openlp.plugins.bibles.lib.db.Manager')
self.registry_patcher = patch('openlp.plugins.bibles.lib.db.Registry')
self.addCleanup(self.manager_patcher.stop)
self.addCleanup(self.registry_patcher.stop)
self.manager_patcher.start()
def tearDown(self):
self.registry_patcher.stop()
self.manager_patcher.stop()
self.registry_patcher.start()
def test_create_importer(self):
"""
@ -58,12 +60,331 @@ class TestCSVImport(TestCase):
mocked_manager = MagicMock()
# WHEN: An importer object is created
importer = CSVBible(mocked_manager, path='.', name='.', booksfile='.', versefile='.')
importer = CSVBible(mocked_manager, path='.', name='.', booksfile='books.csv', versefile='verse.csv')
# THEN: The importer should be an instance of BibleDB
self.assertIsInstance(importer, BibleDB)
# THEN: The importer should be an instance of BibleImport
self.assertIsInstance(importer, BibleImport)
self.assertEqual(importer.books_file, 'books.csv')
self.assertEqual(importer.verses_file, 'verse.csv')
def test_file_import(self):
def book_namedtuple_test(self):
"""
Test that the Book namedtuple is created as expected
"""
# GIVEN: The Book namedtuple
# WHEN: Creating an instance of Book
result = Book('id', 'testament_id', 'name', 'abbreviation')
# THEN: The attributes should match up with the data we used
self.assertEqual(result.id, 'id')
self.assertEqual(result.testament_id, 'testament_id')
self.assertEqual(result.name, 'name')
self.assertEqual(result.abbreviation, 'abbreviation')
def verse_namedtuple_test(self):
"""
Test that the Verse namedtuple is created as expected
"""
# GIVEN: The Verse namedtuple
# WHEN: Creating an instance of Verse
result = Verse('book_id_name', 'chapter_number', 'number', 'text')
# THEN: The attributes should match up with the data we used
self.assertEqual(result.book_id_name, 'book_id_name')
self.assertEqual(result.chapter_number, 'chapter_number')
self.assertEqual(result.number, 'number')
self.assertEqual(result.text, 'text')
def get_book_name_id_test(self):
"""
Test that get_book_name() returns the correct book when called with an id
"""
# GIVEN: A dictionary of books with their id as the keys
books = {1: 'Book 1', 2: 'Book 2', 3: 'Book 3'}
# WHEN: Calling get_book_name() and the name is an integer represented as a string
test_data = [['1', 'Book 1'], ['2', 'Book 2'], ['3', 'Book 3']]
for name, expected_result in test_data:
actual_result = CSVBible.get_book_name(name, books)
# THEN: get_book_name() should return the book name associated with that id from the books dictionary
self.assertEqual(actual_result, expected_result)
def get_book_name_test(self):
"""
Test that get_book_name() returns the name when called with a non integer value
"""
# GIVEN: A dictionary of books with their id as the keys
books = {1: 'Book 1', 2: 'Book 2', 3: 'Book 3'}
# WHEN: Calling get_book_name() and the name is not an integer represented as a string
test_data = [['Book 4', 'Book 4'], ['Book 5', 'Book 5'], ['Book 6', 'Book 6']]
for name, expected_result in test_data:
actual_result = CSVBible.get_book_name(name, books)
# THEN: get_book_name() should return the input
self.assertEqual(actual_result, expected_result)
def parse_csv_file_test(self):
"""
Test the parse_csv_file() with sample data
"""
# GIVEN: A mocked csv.reader which returns an iterator with test data
test_data = [['1', 'Line 1', 'Data 1'], ['2', 'Line 2', 'Data 2'], ['3', 'Line 3', 'Data 3']]
TestTuple = namedtuple('TestTuple', 'line_no line_description line_data')
with patch('openlp.plugins.bibles.lib.importers.csvbible.get_file_encoding',
return_value={'encoding': 'utf-8', 'confidence': 0.99}),\
patch('openlp.plugins.bibles.lib.importers.csvbible.open', create=True) as mocked_open,\
patch('openlp.plugins.bibles.lib.importers.csvbible.csv.reader',
return_value=iter(test_data)) as mocked_reader:
# WHEN: Calling the CSVBible parse_csv_file method with a file name and TestTuple
result = CSVBible.parse_csv_file('file.csv', TestTuple)
# THEN: A list of TestTuple instances with the parsed data should be returned
self.assertEqual(result, [TestTuple('1', 'Line 1', 'Data 1'), TestTuple('2', 'Line 2', 'Data 2'),
TestTuple('3', 'Line 3', 'Data 3')])
mocked_open.assert_called_once_with('file.csv', 'r', encoding='utf-8', newline='')
mocked_reader.assert_called_once_with(ANY, delimiter=',', quotechar='"')
def parse_csv_file_oserror_test(self):
"""
Test the parse_csv_file() handles an OSError correctly
"""
# GIVEN: Mocked a mocked open object which raises an OSError
with patch('openlp.plugins.bibles.lib.importers.csvbible.get_file_encoding',
return_value={'encoding': 'utf-8', 'confidence': 0.99}),\
patch('openlp.plugins.bibles.lib.importers.csvbible.open', side_effect=OSError, create=True):
# WHEN: Calling CSVBible.parse_csv_file
# THEN: A ValidationError should be raised
with self.assertRaises(ValidationError) as context:
CSVBible.parse_csv_file('file.csv', None)
self.assertEqual(context.exception.msg, 'Parsing "file.csv" failed')
def parse_csv_file_csverror_test(self):
"""
Test the parse_csv_file() handles an csv.Error correctly
"""
# GIVEN: Mocked a csv.reader which raises an csv.Error
with patch('openlp.plugins.bibles.lib.importers.csvbible.get_file_encoding',
return_value={'encoding': 'utf-8', 'confidence': 0.99}),\
patch('openlp.plugins.bibles.lib.importers.csvbible.open', create=True),\
patch('openlp.plugins.bibles.lib.importers.csvbible.csv.reader', side_effect=csv.Error):
# WHEN: Calling CSVBible.parse_csv_file
# THEN: A ValidationError should be raised
with self.assertRaises(ValidationError) as context:
CSVBible.parse_csv_file('file.csv', None)
self.assertEqual(context.exception.msg, 'Parsing "file.csv" failed')
def process_books_stopped_import_test(self):
"""
Test process books when the import is stopped
"""
# GIVEN: An instance of CSVBible with the stop_import_flag set to True
mocked_manager = MagicMock()
with patch('openlp.plugins.bibles.lib.db.BibleDB._setup'):
importer = CSVBible(mocked_manager, path='.', name='.', booksfile='books.csv', versefile='verse.csv')
type(importer).application = PropertyMock()
importer.stop_import_flag = True
importer.wizard = MagicMock()
# WHEN: Calling process_books
result = importer.process_books(['Book 1'])
# THEN: increment_progress_bar should not be called and the return value should be None
self.assertFalse(importer.wizard.increment_progress_bar.called)
self.assertIsNone(result)
def process_books_test(self):
"""
Test process books when it completes successfully
"""
# GIVEN: An instance of CSVBible with the stop_import_flag set to False, and some sample data
mocked_manager = MagicMock()
with patch('openlp.plugins.bibles.lib.db.BibleDB._setup'),\
patch('openlp.plugins.bibles.lib.importers.csvbible.translate'):
importer = CSVBible(mocked_manager, path='.', name='.', booksfile='books.csv', versefile='verse.csv')
type(importer).application = PropertyMock()
importer.find_and_create_book = MagicMock()
importer.language_id = 10
importer.stop_import_flag = False
importer.wizard = MagicMock()
books = [Book('1', '1', '1. Mosebog', '1Mos'), Book('2', '1', '2. Mosebog', '2Mos')]
# WHEN: Calling process_books
result = importer.process_books(books)
# THEN: translate and find_and_create_book should have been called with both book names.
# The returned data should be a dictionary with both song's id and names.
self.assertEqual(importer.find_and_create_book.mock_calls,
[call('1. Mosebog', 2, 10), call('2. Mosebog', 2, 10)])
importer.application.process_events.assert_called_once_with()
self.assertDictEqual(result, {1: '1. Mosebog', 2: '2. Mosebog'})
def process_verses_stopped_import_test(self):
"""
Test process_verses when the import is stopped
"""
# GIVEN: An instance of CSVBible with the stop_import_flag set to True
mocked_manager = MagicMock()
with patch('openlp.plugins.bibles.lib.db.BibleDB._setup'):
importer = CSVBible(mocked_manager, path='.', name='.', booksfile='books.csv', versefile='verse.csv')
type(importer).application = PropertyMock()
importer.get_book_name = MagicMock()
importer.session = MagicMock()
importer.stop_import_flag = True
importer.wizard = MagicMock()
# WHEN: Calling process_verses
result = importer.process_verses([], [])
# THEN: get_book_name should not be called and the return value should be None
self.assertFalse(importer.get_book_name.called)
importer.wizard.increment_progress_bar.assert_called_once_with('Importing verses... done.')
importer.application.process_events.assert_called_once_with()
self.assertIsNone(result)
def process_verses_successful_test(self):
"""
Test process_verses when the import is successful
"""
# GIVEN: An instance of CSVBible with the application and wizard attributes mocked out, and some test data.
mocked_manager = MagicMock()
with patch('openlp.plugins.bibles.lib.db.BibleDB._setup'),\
patch('openlp.plugins.bibles.lib.importers.csvbible.translate'):
importer = CSVBible(mocked_manager, path='.', name='.', booksfile='books.csv', versefile='verse.csv')
type(importer).application = PropertyMock()
importer.create_verse = MagicMock()
importer.get_book = MagicMock(return_value=Book('1', '1', '1. Mosebog', '1Mos'))
importer.get_book_name = MagicMock(return_value='1. Mosebog')
importer.session = MagicMock()
importer.stop_import_flag = False
importer.wizard = MagicMock()
verses = [Verse(1, 1, 1, 'I Begyndelsen skabte Gud Himmelen og Jorden.'),
Verse(1, 1, 2, 'Og Jorden var øde og tom, og der var Mørke over Verdensdybet. '
'Men Guds Ånd svævede over Vandene.')]
books = {1: '1. Mosebog'}
# WHEN: Calling process_verses
importer.process_verses(verses, books)
# THEN: create_verse is called with the test data
self.assertEqual(importer.get_book_name.mock_calls, [call(1, books), call(1, books)])
importer.get_book.assert_called_once_with('1. Mosebog')
self.assertEqual(importer.session.commit.call_count, 2)
self.assertEqual(importer.create_verse.mock_calls,
[call('1', 1, 1, 'I Begyndelsen skabte Gud Himmelen og Jorden.'),
call('1', 1, 2, 'Og Jorden var øde og tom, og der var Mørke over Verdensdybet. '
'Men Guds Ånd svævede over Vandene.')])
importer.application.process_events.assert_called_once_with()
def do_import_invalid_language_id_test(self):
"""
Test do_import when the user cancels the language selection dialog box
"""
# GIVEN: An instance of CSVBible and a mocked get_language which simulates the user cancelling the language box
mocked_manager = MagicMock()
with patch('openlp.plugins.bibles.lib.db.BibleDB._setup'),\
patch('openlp.plugins.bibles.lib.importers.csvbible.log') as mocked_log:
importer = CSVBible(mocked_manager, path='.', name='.', booksfile='books.csv', versefile='verse.csv')
importer.get_language = MagicMock(return_value=None)
# WHEN: Calling do_import
result = importer.do_import('Bible Name')
# THEN: The log.exception method should have been called to show that it reached the except clause.
# False should be returned.
importer.get_language.assert_called_once_with('Bible Name')
mocked_log.exception.assert_called_once_with('Could not import CSV bible')
self.assertFalse(result)
def do_import_stop_import_test(self):
"""
Test do_import when the import is stopped
"""
# GIVEN: An instance of CSVBible with stop_import set to True
mocked_manager = MagicMock()
with patch('openlp.plugins.bibles.lib.db.BibleDB._setup'),\
patch('openlp.plugins.bibles.lib.importers.csvbible.log') as mocked_log:
importer = CSVBible(mocked_manager, path='.', name='.', booksfile='books.csv', versefile='verse.csv')
importer.get_language = MagicMock(return_value=10)
importer.parse_csv_file = MagicMock(return_value=['Book 1', 'Book 2', 'Book 3'])
importer.process_books = MagicMock()
importer.stop_import_flag = True
importer.wizard = MagicMock()
# WHEN: Calling do_import
result = importer.do_import('Bible Name')
# THEN: log.exception should not be called, parse_csv_file should only be called once,
# and False should be returned.
self.assertFalse(mocked_log.exception.called)
importer.parse_csv_file.assert_called_once_with('books.csv', Book)
importer.process_books.assert_called_once_with(['Book 1', 'Book 2', 'Book 3'])
self.assertFalse(result)
def do_import_stop_import_2_test(self):
"""
Test do_import when the import is stopped
"""
# GIVEN: An instance of CSVBible with stop_import which is True the second time of calling
mocked_manager = MagicMock()
with patch('openlp.plugins.bibles.lib.db.BibleDB._setup'),\
patch('openlp.plugins.bibles.lib.importers.csvbible.log') as mocked_log:
CSVBible.stop_import_flag = PropertyMock(side_effect=[False, True])
importer = CSVBible(mocked_manager, path='.', name='.', booksfile='books.csv', versefile='verses.csv')
importer.get_language = MagicMock(return_value=10)
importer.parse_csv_file = MagicMock(side_effect=[['Book 1'], ['Verse 1']])
importer.process_books = MagicMock(return_value=['Book 1'])
importer.process_verses = MagicMock(return_value=['Verse 1'])
importer.wizard = MagicMock()
# WHEN: Calling do_import
result = importer.do_import('Bible Name')
# THEN: log.exception should not be called, parse_csv_file should be called twice,
# and False should be returned.
self.assertFalse(mocked_log.exception.called)
self.assertEqual(importer.parse_csv_file.mock_calls, [call('books.csv', Book), call('verses.csv', Verse)])
importer.process_verses.assert_called_once_with(['Verse 1'], ['Book 1'])
self.assertFalse(result)
# Cleanup
del CSVBible.stop_import_flag
def do_import_success_test(self):
"""
Test do_import when the import succeeds
"""
# GIVEN: An instance of CSVBible
mocked_manager = MagicMock()
with patch('openlp.plugins.bibles.lib.db.BibleDB._setup'),\
patch('openlp.plugins.bibles.lib.importers.csvbible.log') as mocked_log:
importer = CSVBible(mocked_manager, path='.', name='.', booksfile='books.csv', versefile='verses.csv')
importer.get_language = MagicMock(return_value=10)
importer.parse_csv_file = MagicMock(side_effect=[['Book 1'], ['Verse 1']])
importer.process_books = MagicMock(return_value=['Book 1'])
importer.process_verses = MagicMock(return_value=['Verse 1'])
importer.session = MagicMock()
importer.stop_import_flag = False
importer.wizard = MagicMock()
# WHEN: Calling do_import
result = importer.do_import('Bible Name')
# THEN: log.exception should not be called, parse_csv_file should be called twice,
# and True should be returned.
self.assertFalse(mocked_log.exception.called)
self.assertEqual(importer.parse_csv_file.mock_calls, [call('books.csv', Book), call('verses.csv', Verse)])
importer.process_books.assert_called_once_with(['Book 1'])
importer.process_verses.assert_called_once_with(['Verse 1'], ['Book 1'])
self.assertTrue(result)
def file_import_test(self):
"""
Test the actual import of CSV Bible file
"""
@ -73,7 +394,7 @@ class TestCSVImport(TestCase):
test_data = json.loads(result_file.read().decode())
books_file = os.path.join(TEST_PATH, 'dk1933-books.csv')
verses_file = os.path.join(TEST_PATH, 'dk1933-verses.csv')
with patch('openlp.plugins.bibles.lib.csvbible.CSVBible.application'):
with patch('openlp.plugins.bibles.lib.importers.csvbible.CSVBible.application'):
mocked_manager = MagicMock()
mocked_import_wizard = MagicMock()
importer = CSVBible(mocked_manager, path='.', name='.', booksfile=books_file, versefile=verses_file)

View File

@ -26,7 +26,7 @@ from unittest import TestCase
from bs4 import BeautifulSoup
from tests.functional import patch, MagicMock
from openlp.plugins.bibles.lib.http import BSExtract
from openlp.plugins.bibles.lib.importers.http import BSExtract
# TODO: Items left to test
# BGExtract
@ -68,11 +68,11 @@ class TestBSExtract(TestCase):
# get_books_from_http
# _get_application
def setUp(self):
self.get_soup_for_bible_ref_patcher = patch('openlp.plugins.bibles.lib.http.get_soup_for_bible_ref')
self.log_patcher = patch('openlp.plugins.bibles.lib.http.log')
self.send_error_message_patcher = patch('openlp.plugins.bibles.lib.http.send_error_message')
self.socket_patcher = patch('openlp.plugins.bibles.lib.http.socket')
self.urllib_patcher = patch('openlp.plugins.bibles.lib.http.urllib')
self.get_soup_for_bible_ref_patcher = patch('openlp.plugins.bibles.lib.importers.http.get_soup_for_bible_ref')
self.log_patcher = patch('openlp.plugins.bibles.lib.importers.http.log')
self.send_error_message_patcher = patch('openlp.plugins.bibles.lib.importers.http.send_error_message')
self.socket_patcher = patch('openlp.plugins.bibles.lib.importers.http.socket')
self.urllib_patcher = patch('openlp.plugins.bibles.lib.importers.http.urllib')
self.mock_get_soup_for_bible_ref = self.get_soup_for_bible_ref_patcher.start()
self.mock_log = self.log_patcher.start()

View File

@ -28,7 +28,7 @@ import json
from unittest import TestCase
from tests.functional import MagicMock, patch
from openlp.plugins.bibles.lib.opensong import OpenSongBible
from openlp.plugins.bibles.lib.importers.opensong import OpenSongBible
from openlp.plugins.bibles.lib.db import BibleDB
TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__),
@ -72,7 +72,7 @@ class TestOpenSongImport(TestCase):
result_file = open(os.path.join(TEST_PATH, 'dk1933.json'), 'rb')
test_data = json.loads(result_file.read().decode())
bible_file = 'opensong-dk1933.xml'
with patch('openlp.plugins.bibles.lib.opensong.OpenSongBible.application'):
with patch('openlp.plugins.bibles.lib.importers.opensong.OpenSongBible.application'):
mocked_manager = MagicMock()
mocked_import_wizard = MagicMock()
importer = OpenSongBible(mocked_manager, path='.', name='.', filename='')
@ -98,7 +98,7 @@ class TestOpenSongImport(TestCase):
Test that we give an error message if trying to import a zefania bible
"""
# GIVEN: A mocked out "manager" and mocked out critical_error_message_box and an import
with patch('openlp.plugins.bibles.lib.opensong.critical_error_message_box') as \
with patch('openlp.plugins.bibles.lib.importers.opensong.critical_error_message_box') as \
mocked_critical_error_message_box:
mocked_manager = MagicMock()
importer = OpenSongBible(mocked_manager, path='.', name='.', filename='')

View File

@ -28,7 +28,7 @@ import json
from unittest import TestCase
from tests.functional import MagicMock, patch
from openlp.plugins.bibles.lib.osis import OSISBible
from openlp.plugins.bibles.lib.importers.osis import OSISBible
from openlp.plugins.bibles.lib.db import BibleDB
TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__),
@ -72,7 +72,7 @@ class TestOsisImport(TestCase):
result_file = open(os.path.join(TEST_PATH, 'dk1933.json'), 'rb')
test_data = json.loads(result_file.read().decode())
bible_file = 'osis-dk1933.xml'
with patch('openlp.plugins.bibles.lib.osis.OSISBible.application'):
with patch('openlp.plugins.bibles.lib.importers.osis.OSISBible.application'):
mocked_manager = MagicMock()
mocked_import_wizard = MagicMock()
importer = OSISBible(mocked_manager, path='.', name='.', filename='')
@ -102,7 +102,7 @@ class TestOsisImport(TestCase):
result_file = open(os.path.join(TEST_PATH, 'kjv.json'), 'rb')
test_data = json.loads(result_file.read().decode())
bible_file = 'osis-kjv.xml'
with patch('openlp.plugins.bibles.lib.osis.OSISBible.application'):
with patch('openlp.plugins.bibles.lib.importers.osis.OSISBible.application'):
mocked_manager = MagicMock()
mocked_import_wizard = MagicMock()
importer = OSISBible(mocked_manager, path='.', name='.', filename='')
@ -132,7 +132,7 @@ class TestOsisImport(TestCase):
result_file = open(os.path.join(TEST_PATH, 'web.json'), 'rb')
test_data = json.loads(result_file.read().decode())
bible_file = 'osis-web.xml'
with patch('openlp.plugins.bibles.lib.osis.OSISBible.application'):
with patch('openlp.plugins.bibles.lib.importers.osis.OSISBible.application'):
mocked_manager = MagicMock()
mocked_import_wizard = MagicMock()
importer = OSISBible(mocked_manager, path='.', name='.', filename='')
@ -162,7 +162,7 @@ class TestOsisImport(TestCase):
result_file = open(os.path.join(TEST_PATH, 'dk1933.json'), 'rb')
test_data = json.loads(result_file.read().decode())
bible_file = 'osis-dk1933-empty-verse.xml'
with patch('openlp.plugins.bibles.lib.osis.OSISBible.application'):
with patch('openlp.plugins.bibles.lib.importers.osis.OSISBible.application'):
mocked_manager = MagicMock()
mocked_import_wizard = MagicMock()
importer = OSISBible(mocked_manager, path='.', name='.', filename='')

View File

@ -29,7 +29,7 @@ from unittest import TestCase, skipUnless
from tests.functional import MagicMock, patch
try:
from openlp.plugins.bibles.lib.sword import SwordBible
from openlp.plugins.bibles.lib.importers.sword import SwordBible
HAS_PYSWORD = True
except ImportError:
HAS_PYSWORD = False
@ -68,8 +68,8 @@ class TestSwordImport(TestCase):
# THEN: The importer should be an instance of BibleDB
self.assertIsInstance(importer, BibleDB)
@patch('openlp.plugins.bibles.lib.sword.SwordBible.application')
@patch('openlp.plugins.bibles.lib.sword.modules')
@patch('openlp.plugins.bibles.lib.importers.sword.SwordBible.application')
@patch('openlp.plugins.bibles.lib.importers.sword.modules')
@patch('openlp.core.common.languages')
def test_simple_import(self, mocked_languages, mocked_pysword_modules, mocked_application):
"""

View File

@ -28,7 +28,7 @@ import json
from unittest import TestCase
from tests.functional import MagicMock, patch
from openlp.plugins.bibles.lib.zefania import ZefaniaBible
from openlp.plugins.bibles.lib.importers.zefania import ZefaniaBible
from openlp.plugins.bibles.lib.db import BibleDB
TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__),
@ -72,7 +72,7 @@ class TestZefaniaImport(TestCase):
result_file = open(os.path.join(TEST_PATH, 'dk1933.json'), 'rb')
test_data = json.loads(result_file.read().decode())
bible_file = 'zefania-dk1933.xml'
with patch('openlp.plugins.bibles.lib.zefania.ZefaniaBible.application'):
with patch('openlp.plugins.bibles.lib.importers.zefania.ZefaniaBible.application'):
mocked_manager = MagicMock()
mocked_import_wizard = MagicMock()
importer = ZefaniaBible(mocked_manager, path='.', name='.', filename='')
@ -102,7 +102,7 @@ class TestZefaniaImport(TestCase):
result_file = open(os.path.join(TEST_PATH, 'rst.json'), 'rb')
test_data = json.loads(result_file.read().decode())
bible_file = 'zefania-rst.xml'
with patch('openlp.plugins.bibles.lib.zefania.ZefaniaBible.application'):
with patch('openlp.plugins.bibles.lib.importers.zefania.ZefaniaBible.application'):
mocked_manager = MagicMock()
mocked_import_wizard = MagicMock()
importer = ZefaniaBible(mocked_manager, path='.', name='.', filename='')

View File

@ -26,7 +26,7 @@ class and methods.
import os
from unittest import TestCase
from openlp.core.common import Registry, Settings
from openlp.core.common import Registry
from openlp.core.lib.projector.db import Projector, ProjectorDB
from openlp.core.ui import ProjectorEditForm
@ -63,43 +63,41 @@ class TestProjectorEditForm(TestCase, TestMixin):
:return: None
"""
self.projectordb.session.close()
del(self.projector_form)
del self.projector_form
self.destroy_settings()
def test_edit_form_add_projector(self):
@patch('openlp.core.ui.projector.editform.QtWidgets.QDialog.exec')
def test_edit_form_add_projector(self, mocked_exec):
"""
Test projector edit form with no parameters creates a new entry.
:return: None
"""
# GIVEN: Mocked setup
with patch('openlp.core.ui.projector.editform.QDialog.exec'):
# WHEN: Calling edit form with no parameters
self.projector_form.exec()
item = self.projector_form.projector
# WHEN: Calling edit form with no parameters
self.projector_form.exec()
item = self.projector_form.projector
# THEN: Should be creating a new instance
self.assertTrue(self.projector_form.new_projector,
'Projector edit form should be marked as a new entry')
self.assertTrue((item.ip is None and item.name is None),
'Projector edit form should have a new Projector() instance to edit')
# THEN: Should be creating a new instance
self.assertTrue(self.projector_form.new_projector,
'Projector edit form should be marked as a new entry')
self.assertTrue((item.ip is None and item.name is None),
'Projector edit form should have a new Projector() instance to edit')
def test_edit_form_edit_projector(self):
@patch('openlp.core.ui.projector.editform.QtWidgets.QDialog.exec')
def test_edit_form_edit_projector(self, mocked_exec):
"""
Test projector edit form with existing projector entry
:return:
"""
# GIVEN: Mocked setup
with patch('openlp.core.ui.projector.editform.QDialog.exec'):
# WHEN: Calling edit form with existing projector instance
self.projector_form.exec(projector=Projector(**TEST1_DATA))
item = self.projector_form.projector
# WHEN: Calling edit form with existing projector instance
self.projector_form.exec(projector=Projector(**TEST1_DATA))
item = self.projector_form.projector
# THEN: Should be editing an existing entry
self.assertFalse(self.projector_form.new_projector,
'Projector edit form should be marked as existing entry')
self.assertTrue((item.ip is TEST1_DATA['ip'] and item.name is TEST1_DATA['name']),
'Projector edit form should have TEST1_DATA() instance to edit')
# THEN: Should be editing an existing entry
self.assertFalse(self.projector_form.new_projector,
'Projector edit form should be marked as existing entry')
self.assertTrue((item.ip is TEST1_DATA['ip'] and item.name is TEST1_DATA['name']),
'Projector edit form should have TEST1_DATA() instance to edit')

View File

@ -25,7 +25,7 @@
from unittest import TestCase, skip
from openlp.core.common import Registry
from openlp.plugins.bibles.lib.http import BGExtract, CWExtract, BSExtract
from openlp.plugins.bibles.lib.importers.http import BGExtract, CWExtract, BSExtract
from tests.interfaces import MagicMock