This commit is contained in:
Raoul Snyman 2017-12-11 13:25:58 -07:00
commit 9a14750d65
28 changed files with 672 additions and 312 deletions

View File

@ -52,6 +52,8 @@ def download_sha256():
web_config = get_web_page('https://get.openlp.org/webclient/download.cfg', headers={'User-Agent': user_agent}) web_config = get_web_page('https://get.openlp.org/webclient/download.cfg', headers={'User-Agent': user_agent})
except ConnectionError: except ConnectionError:
return False return False
if not web_config:
return None
file_bits = web_config.split() file_bits = web_config.split()
return file_bits[0], file_bits[2] return file_bits[0], file_bits[2]

View File

@ -67,7 +67,10 @@ class HttpWorker(QtCore.QObject):
address = Settings().value('api/ip address') address = Settings().value('api/ip address')
port = Settings().value('api/port') port = Settings().value('api/port')
Registry().execute('get_website_version') Registry().execute('get_website_version')
serve(application, host=address, port=port) try:
serve(application, host=address, port=port)
except OSError:
log.exception('An error occurred when serving the application.')
def stop(self): def stop(self):
pass pass
@ -82,13 +85,14 @@ class HttpServer(RegistryBase, RegistryProperties, LogMixin):
Initialise the http server, and start the http server Initialise the http server, and start the http server
""" """
super(HttpServer, self).__init__(parent) super(HttpServer, self).__init__(parent)
self.worker = HttpWorker() if Registry().get_flag('no_web_server'):
self.thread = QtCore.QThread() self.worker = HttpWorker()
self.worker.moveToThread(self.thread) self.thread = QtCore.QThread()
self.thread.started.connect(self.worker.run) self.worker.moveToThread(self.thread)
self.thread.start() self.thread.started.connect(self.worker.run)
Registry().register_function('download_website', self.first_time) self.thread.start()
Registry().register_function('get_website_version', self.website_version) Registry().register_function('download_website', self.first_time)
Registry().register_function('get_website_version', self.website_version)
Registry().set_flag('website_version', '0.0') Registry().set_flag('website_version', '0.0')
def bootstrap_post_set_up(self): def bootstrap_post_set_up(self):

View File

@ -70,12 +70,13 @@ class WebSocketServer(RegistryProperties, LogMixin):
Initialise and start the WebSockets server Initialise and start the WebSockets server
""" """
super(WebSocketServer, self).__init__() super(WebSocketServer, self).__init__()
self.settings_section = 'api' if Registry().get_flag('no_web_server'):
self.worker = WebSocketWorker(self) self.settings_section = 'api'
self.thread = QtCore.QThread() self.worker = WebSocketWorker(self)
self.worker.moveToThread(self.thread) self.thread = QtCore.QThread()
self.thread.started.connect(self.worker.run) self.worker.moveToThread(self.thread)
self.thread.start() self.thread.started.connect(self.worker.run)
self.thread.start()
def start_server(self): def start_server(self):
""" """

View File

@ -403,8 +403,8 @@ def main(args=None):
.format(back_up_path=back_up_path)) .format(back_up_path=back_up_path))
QtWidgets.QMessageBox.information( QtWidgets.QMessageBox.information(
None, translate('OpenLP', 'Settings Upgrade'), None, translate('OpenLP', 'Settings Upgrade'),
translate('OpenLP', 'Your settings are about to upgraded. A backup will be created at {back_up_path}') translate('OpenLP', 'Your settings are about to be upgraded. A backup will be created at '
.format(back_up_path=back_up_path)) '{back_up_path}').format(back_up_path=back_up_path))
settings.export(back_up_path) settings.export(back_up_path)
settings.upgrade_settings() settings.upgrade_settings()
# First time checks in settings # First time checks in settings

View File

@ -260,7 +260,7 @@ class Settings(QtCore.QSettings):
('bibles/last search type', '', []), ('bibles/last search type', '', []),
('custom/last search type', 'custom/last used search type', []), ('custom/last search type', 'custom/last used search type', []),
# The following changes are being made for the conversion to using Path objects made in 2.6 development # The following changes are being made for the conversion to using Path objects made in 2.6 development
('advanced/data path', 'advanced/data path', [(str_to_path, None)]), ('advanced/data path', 'advanced/data path', [(lambda p: Path(p) if p is not None else None, None)]),
('crashreport/last directory', 'crashreport/last directory', [(str_to_path, None)]), ('crashreport/last directory', 'crashreport/last directory', [(str_to_path, None)]),
('servicemanager/last directory', 'servicemanager/last directory', [(str_to_path, None)]), ('servicemanager/last directory', 'servicemanager/last directory', [(str_to_path, None)]),
('servicemanager/last file', 'servicemanager/last file', [(str_to_path, None)]), ('servicemanager/last file', 'servicemanager/last file', [(str_to_path, None)]),

View File

@ -25,8 +25,6 @@
Initialization for the openlp.core.projectors modules. Initialization for the openlp.core.projectors modules.
""" """
from openlp.core.projectors.constants import PJLINK_PORT, ERROR_MSG, ERROR_STRING
class DialogSourceStyle(object): class DialogSourceStyle(object):
""" """

View File

@ -144,6 +144,24 @@ PJLINK_VALID_CMD = {
} }
} }
# QAbstractSocketState enums converted to string
S_QSOCKET_STATE = {
0: 'QSocketState - UnconnectedState',
1: 'QSocketState - HostLookupState',
2: 'QSocketState - ConnectingState',
3: 'QSocketState - ConnectedState',
4: 'QSocketState - BoundState',
5: 'QSocketState - ListeningState (internal use only)',
6: 'QSocketState - ClosingState',
'UnconnectedState': 0,
'HostLookupState': 1,
'ConnectingState': 2,
'ConnectedState': 3,
'BoundState': 4,
'ListeningState': 5,
'ClosingState': 6
}
# Error and status codes # Error and status codes
S_OK = E_OK = 0 # E_OK included since I sometimes forget S_OK = E_OK = 0 # E_OK included since I sometimes forget
# Error codes. Start at 200 so we don't duplicate system error codes. # Error codes. Start at 200 so we don't duplicate system error codes.

View File

@ -415,7 +415,7 @@ class ProjectorDB(Manager):
for key in projector.source_available: for key in projector.source_available:
item = self.get_object_filtered(ProjectorSource, item = self.get_object_filtered(ProjectorSource,
and_(ProjectorSource.code == key, and_(ProjectorSource.code == key,
ProjectorSource.projector_id == projector.dbid)) ProjectorSource.projector_id == projector.id))
if item is None: if item is None:
source_dict[key] = PJLINK_DEFAULT_CODES[key] source_dict[key] = PJLINK_DEFAULT_CODES[key]
else: else:

View File

@ -58,8 +58,7 @@ from openlp.core.projectors.constants import CONNECTION_ERRORS, CR, ERROR_MSG, E
E_AUTHENTICATION, E_CONNECTION_REFUSED, E_GENERAL, E_INVALID_DATA, E_NETWORK, E_NOT_CONNECTED, E_OK, \ E_AUTHENTICATION, E_CONNECTION_REFUSED, E_GENERAL, E_INVALID_DATA, E_NETWORK, E_NOT_CONNECTED, E_OK, \
E_PARAMETER, E_PROJECTOR, E_SOCKET_TIMEOUT, E_UNAVAILABLE, E_UNDEFINED, PJLINK_ERRORS, PJLINK_ERST_DATA, \ E_PARAMETER, E_PROJECTOR, E_SOCKET_TIMEOUT, E_UNAVAILABLE, E_UNDEFINED, PJLINK_ERRORS, PJLINK_ERST_DATA, \
PJLINK_ERST_STATUS, PJLINK_MAX_PACKET, PJLINK_PORT, PJLINK_POWR_STATUS, PJLINK_VALID_CMD, \ PJLINK_ERST_STATUS, PJLINK_MAX_PACKET, PJLINK_PORT, PJLINK_POWR_STATUS, PJLINK_VALID_CMD, \
STATUS_STRING, S_CONNECTED, S_CONNECTING, S_INFO, S_NETWORK_RECEIVED, S_NETWORK_SENDING, \ STATUS_STRING, S_CONNECTED, S_CONNECTING, S_INFO, S_NOT_CONNECTED, S_OFF, S_OK, S_ON, S_QSOCKET_STATE, S_STATUS
S_NOT_CONNECTED, S_OFF, S_OK, S_ON, S_STATUS
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
log.debug('pjlink loaded') log.debug('pjlink loaded')
@ -111,7 +110,7 @@ class PJLinkCommands(object):
""" """
log.debug('PJlinkCommands(args={args} kwargs={kwargs})'.format(args=args, kwargs=kwargs)) log.debug('PJlinkCommands(args={args} kwargs={kwargs})'.format(args=args, kwargs=kwargs))
super().__init__() super().__init__()
# Map command to function # Map PJLink command to method
self.pjlink_functions = { self.pjlink_functions = {
'AVMT': self.process_avmt, 'AVMT': self.process_avmt,
'CLSS': self.process_clss, 'CLSS': self.process_clss,
@ -123,7 +122,9 @@ class PJLinkCommands(object):
'INST': self.process_inst, 'INST': self.process_inst,
'LAMP': self.process_lamp, 'LAMP': self.process_lamp,
'NAME': self.process_name, 'NAME': self.process_name,
'PJLINK': self.check_login, 'PJLINK': self.process_pjlink,
# TODO: Part of check_login refactor - remove when done
# 'PJLINK': self.check_login,
'POWR': self.process_powr, 'POWR': self.process_powr,
'SNUM': self.process_snum, 'SNUM': self.process_snum,
'SVER': self.process_sver, 'SVER': self.process_sver,
@ -135,7 +136,8 @@ class PJLinkCommands(object):
""" """
Initialize instance variables. Also used to reset projector-specific information to default. Initialize instance variables. Also used to reset projector-specific information to default.
""" """
log.debug('({ip}) reset_information() connect status is {state}'.format(ip=self.ip, state=self.state())) log.debug('({ip}) reset_information() connect status is {state}'.format(ip=self.ip,
state=S_QSOCKET_STATE[self.state()]))
self.fan = None # ERST self.fan = None # ERST
self.filter_time = None # FILT self.filter_time = None # FILT
self.lamp = None # LAMP self.lamp = None # LAMP
@ -165,6 +167,7 @@ class PJLinkCommands(object):
self.socket_timer.stop() self.socket_timer.stop()
self.send_busy = False self.send_busy = False
self.send_queue = [] self.send_queue = []
self.priority_queue = []
def process_command(self, cmd, data): def process_command(self, cmd, data):
""" """
@ -176,18 +179,19 @@ class PJLinkCommands(object):
log.debug('({ip}) Processing command "{cmd}" with data "{data}"'.format(ip=self.ip, log.debug('({ip}) Processing command "{cmd}" with data "{data}"'.format(ip=self.ip,
cmd=cmd, cmd=cmd,
data=data)) data=data))
# Check if we have a future command not available yet # cmd should already be in uppercase, but data may be in mixed-case.
_cmd = cmd.upper() # Due to some replies should stay as mixed-case, validate using separate uppercase check
_data = data.upper() _data = data.upper()
if _cmd not in PJLINK_VALID_CMD: # Check if we have a future command not available yet
log.error("({ip}) Ignoring command='{cmd}' (Invalid/Unknown)".format(ip=self.ip, cmd=cmd)) if cmd not in PJLINK_VALID_CMD:
log.error('({ip}) Ignoring command="{cmd}" (Invalid/Unknown)'.format(ip=self.ip, cmd=cmd))
return return
elif _data == 'OK': elif _data == 'OK':
log.debug('({ip}) Command "{cmd}" returned OK'.format(ip=self.ip, cmd=cmd)) log.debug('({ip}) Command "{cmd}" returned OK'.format(ip=self.ip, cmd=cmd))
# A command returned successfully, no further processing needed # A command returned successfully, so do a query on command to verify status
return return self.send_command(cmd=cmd)
elif _cmd not in self.pjlink_functions: elif cmd not in self.pjlink_functions:
log.warning("({ip}) Unable to process command='{cmd}' (Future option)".format(ip=self.ip, cmd=cmd)) log.warning('({ip}) Unable to process command="{cmd}" (Future option?)'.format(ip=self.ip, cmd=cmd))
return return
elif _data in PJLINK_ERRORS: elif _data in PJLINK_ERRORS:
# Oops - projector error # Oops - projector error
@ -211,12 +215,10 @@ class PJLinkCommands(object):
elif _data == PJLINK_ERRORS[E_PROJECTOR]: elif _data == PJLINK_ERRORS[E_PROJECTOR]:
# Projector/display error # Projector/display error
self.change_status(E_PROJECTOR) self.change_status(E_PROJECTOR)
self.receive_data_signal()
return return
# Command checks already passed # Command checks already passed
log.debug('({ip}) Calling function for {cmd}'.format(ip=self.ip, cmd=cmd)) log.debug('({ip}) Calling function for {cmd}'.format(ip=self.ip, cmd=cmd))
self.receive_data_signal() self.pjlink_functions[cmd](data)
self.pjlink_functions[_cmd](data)
def process_avmt(self, data): def process_avmt(self, data):
""" """
@ -259,19 +261,19 @@ class PJLinkCommands(object):
# : Received: '%1CLSS=Class 1' (Optoma) # : Received: '%1CLSS=Class 1' (Optoma)
# : Received: '%1CLSS=Version1' (BenQ) # : Received: '%1CLSS=Version1' (BenQ)
if len(data) > 1: if len(data) > 1:
log.warning("({ip}) Non-standard CLSS reply: '{data}'".format(ip=self.ip, data=data)) log.warning('({ip}) Non-standard CLSS reply: "{data}"'.format(ip=self.ip, data=data))
# Due to stupid projectors not following standards (Optoma, BenQ comes to mind), # Due to stupid projectors not following standards (Optoma, BenQ comes to mind),
# AND the different responses that can be received, the semi-permanent way to # AND the different responses that can be received, the semi-permanent way to
# fix the class reply is to just remove all non-digit characters. # fix the class reply is to just remove all non-digit characters.
try: try:
clss = re.findall('\d', data)[0] # Should only be the first match clss = re.findall('\d', data)[0] # Should only be the first match
except IndexError: except IndexError:
log.error("({ip}) No numbers found in class version reply '{data}' - " log.error('({ip}) No numbers found in class version reply "{data}" - '
"defaulting to class '1'".format(ip=self.ip, data=data)) 'defaulting to class "1"'.format(ip=self.ip, data=data))
clss = '1' clss = '1'
elif not data.isdigit(): elif not data.isdigit():
log.error("({ip}) NAN clss version reply '{data}' - " log.error('({ip}) NAN CLSS version reply "{data}" - '
"defaulting to class '1'".format(ip=self.ip, data=data)) 'defaulting to class "1"'.format(ip=self.ip, data=data))
clss = '1' clss = '1'
else: else:
clss = data clss = data
@ -289,7 +291,7 @@ class PJLinkCommands(object):
""" """
if len(data) != PJLINK_ERST_DATA['DATA_LENGTH']: if len(data) != PJLINK_ERST_DATA['DATA_LENGTH']:
count = PJLINK_ERST_DATA['DATA_LENGTH'] count = PJLINK_ERST_DATA['DATA_LENGTH']
log.warning("{ip}) Invalid error status response '{data}': length != {count}".format(ip=self.ip, log.warning('{ip}) Invalid error status response "{data}": length != {count}'.format(ip=self.ip,
data=data, data=data,
count=count)) count=count))
return return
@ -297,7 +299,7 @@ class PJLinkCommands(object):
datacheck = int(data) datacheck = int(data)
except ValueError: except ValueError:
# Bad data - ignore # Bad data - ignore
log.warning("({ip}) Invalid error status response '{data}'".format(ip=self.ip, data=data)) log.warning('({ip}) Invalid error status response "{data}"'.format(ip=self.ip, data=data))
return return
if datacheck == 0: if datacheck == 0:
self.projector_errors = None self.projector_errors = None
@ -430,6 +432,51 @@ class PJLinkCommands(object):
log.debug('({ip}) Setting projector PJLink name to "{data}"'.format(ip=self.ip, data=self.pjlink_name)) log.debug('({ip}) Setting projector PJLink name to "{data}"'.format(ip=self.ip, data=self.pjlink_name))
return return
def process_pjlink(self, data):
"""
Process initial socket connection to terminal.
:param data: Initial packet with authentication scheme
"""
log.debug('({ip}) Processing PJLINK command'.format(ip=self.ip))
chk = data.split(' ')
if len(chk[0]) != 1:
# Invalid - after splitting, first field should be 1 character, either '0' or '1' only
log.error('({ip}) Invalid initial authentication scheme - aborting'.format(ip=self.ip))
return self.disconnect_from_host()
elif chk[0] == '0':
# Normal connection no authentication
if len(chk) > 1:
# Invalid data - there should be nothing after a normal authentication scheme
log.error('({ip}) Normal connection with extra information - aborting'.format(ip=self.ip))
return self.disconnect_from_host()
elif self.pin:
log.error('({ip}) Normal connection but PIN set - aborting'.format(ip=self.ip))
return self.disconnect_from_host()
else:
data_hash = None
elif chk[0] == '1':
if len(chk) < 2:
# Not enough information for authenticated connection
log.error('({ip}) Authenticated connection but not enough info - aborting'.format(ip=self.ip))
return self.disconnect_from_host()
elif not self.pin:
log.error('({ip}) Authenticate connection but no PIN - aborting'.format(ip=self.ip))
return self.disconnect_from_host()
else:
data_hash = str(qmd5_hash(salt=chk[1].encode('utf-8'), data=self.pin.encode('utf-8')),
encoding='ascii')
# Passed basic checks, so start connection
self.readyRead.connect(self.get_socket)
if not self.no_poll:
log.debug('({ip}) process_pjlink(): Starting timer'.format(ip=self.ip))
self.timer.setInterval(2000) # Set 2 seconds for initial information
self.timer.start()
self.change_status(S_CONNECTED)
log.debug('({ip}) process_pjlink(): Sending "CLSS" initial command'.format(ip=self.ip))
# Since this is an initial connection, make it a priority just in case
return self.send_command(cmd="CLSS", salt=data_hash, priority=True)
def process_powr(self, data): def process_powr(self, data):
""" """
Power status. See PJLink specification for format. Power status. See PJLink specification for format.
@ -450,7 +497,7 @@ class PJLinkCommands(object):
self.send_command('INST') self.send_command('INST')
else: else:
# Log unknown status response # Log unknown status response
log.warning('({ip}) Unknown power response: {data}'.format(ip=self.ip, data=data)) log.warning('({ip}) Unknown power response: "{data}"'.format(ip=self.ip, data=data))
return return
def process_rfil(self, data): def process_rfil(self, data):
@ -460,9 +507,9 @@ class PJLinkCommands(object):
if self.model_filter is None: if self.model_filter is None:
self.model_filter = data self.model_filter = data
else: else:
log.warning("({ip}) Filter model already set".format(ip=self.ip)) log.warning('({ip}) Filter model already set'.format(ip=self.ip))
log.warning("({ip}) Saved model: '{old}'".format(ip=self.ip, old=self.model_filter)) log.warning('({ip}) Saved model: "{old}"'.format(ip=self.ip, old=self.model_filter))
log.warning("({ip}) New model: '{new}'".format(ip=self.ip, new=data)) log.warning('({ip}) New model: "{new}"'.format(ip=self.ip, new=data))
def process_rlmp(self, data): def process_rlmp(self, data):
""" """
@ -471,9 +518,9 @@ class PJLinkCommands(object):
if self.model_lamp is None: if self.model_lamp is None:
self.model_lamp = data self.model_lamp = data
else: else:
log.warning("({ip}) Lamp model already set".format(ip=self.ip)) log.warning('({ip}) Lamp model already set'.format(ip=self.ip))
log.warning("({ip}) Saved lamp: '{old}'".format(ip=self.ip, old=self.model_lamp)) log.warning('({ip}) Saved lamp: "{old}"'.format(ip=self.ip, old=self.model_lamp))
log.warning("({ip}) New lamp: '{new}'".format(ip=self.ip, new=data)) log.warning('({ip}) New lamp: "{new}"'.format(ip=self.ip, new=data))
def process_snum(self, data): def process_snum(self, data):
""" """
@ -482,16 +529,16 @@ class PJLinkCommands(object):
:param data: Serial number from projector. :param data: Serial number from projector.
""" """
if self.serial_no is None: if self.serial_no is None:
log.debug("({ip}) Setting projector serial number to '{data}'".format(ip=self.ip, data=data)) log.debug('({ip}) Setting projector serial number to "{data}"'.format(ip=self.ip, data=data))
self.serial_no = data self.serial_no = data
self.db_update = False self.db_update = False
else: else:
# Compare serial numbers and see if we got the same projector # Compare serial numbers and see if we got the same projector
if self.serial_no != data: if self.serial_no != data:
log.warning("({ip}) Projector serial number does not match saved serial number".format(ip=self.ip)) log.warning('({ip}) Projector serial number does not match saved serial number'.format(ip=self.ip))
log.warning("({ip}) Saved: '{old}'".format(ip=self.ip, old=self.serial_no)) log.warning('({ip}) Saved: "{old}"'.format(ip=self.ip, old=self.serial_no))
log.warning("({ip}) Received: '{new}'".format(ip=self.ip, new=data)) log.warning('({ip}) Received: "{new}"'.format(ip=self.ip, new=data))
log.warning("({ip}) NOT saving serial number".format(ip=self.ip)) log.warning('({ip}) NOT saving serial number'.format(ip=self.ip))
self.serial_no_received = data self.serial_no_received = data
def process_sver(self, data): def process_sver(self, data):
@ -500,20 +547,20 @@ class PJLinkCommands(object):
""" """
if len(data) > 32: if len(data) > 32:
# Defined in specs max version is 32 characters # Defined in specs max version is 32 characters
log.warning("Invalid software version - too long") log.warning('Invalid software version - too long')
return return
elif self.sw_version is None: elif self.sw_version is None:
log.debug("({ip}) Setting projector software version to '{data}'".format(ip=self.ip, data=data)) log.debug('({ip}) Setting projector software version to "{data}"'.format(ip=self.ip, data=data))
self.sw_version = data self.sw_version = data
self.db_update = True self.db_update = True
else: else:
# Compare software version and see if we got the same projector # Compare software version and see if we got the same projector
if self.serial_no != data: if self.serial_no != data:
log.warning("({ip}) Projector software version does not match saved " log.warning('({ip}) Projector software version does not match saved '
"software version".format(ip=self.ip)) 'software version'.format(ip=self.ip))
log.warning("({ip}) Saved: '{old}'".format(ip=self.ip, old=self.sw_version)) log.warning('({ip}) Saved: "{old}"'.format(ip=self.ip, old=self.sw_version))
log.warning("({ip}) Received: '{new}'".format(ip=self.ip, new=data)) log.warning('({ip}) Received: "{new}"'.format(ip=self.ip, new=data))
log.warning("({ip}) Saving new serial number as sw_version_received".format(ip=self.ip)) log.warning('({ip}) Saving new serial number as sw_version_received'.format(ip=self.ip))
self.sw_version_received = data self.sw_version_received = data
@ -540,9 +587,9 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands):
:param poll_time: Time (in seconds) to poll connected projector :param poll_time: Time (in seconds) to poll connected projector
:param socket_timeout: Time (in seconds) to abort the connection if no response :param socket_timeout: Time (in seconds) to abort the connection if no response
""" """
log.debug('PJlink(projector={projector}, args={args} kwargs={kwargs})'.format(projector=projector, log.debug('PJlink(projector="{projector}", args="{args}" kwargs="{kwargs}")'.format(projector=projector,
args=args, args=args,
kwargs=kwargs)) kwargs=kwargs))
super().__init__() super().__init__()
self.entry = projector self.entry = projector
self.ip = self.entry.ip self.ip = self.entry.ip
@ -573,6 +620,7 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands):
self.widget = None # QListBox entry self.widget = None # QListBox entry
self.timer = None # Timer that calls the poll_loop self.timer = None # Timer that calls the poll_loop
self.send_queue = [] self.send_queue = []
self.priority_queue = []
self.send_busy = False self.send_busy = False
# Socket timer for some possible brain-dead projectors or network cable pulled # Socket timer for some possible brain-dead projectors or network cable pulled
self.socket_timer = None self.socket_timer = None
@ -586,6 +634,7 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands):
self.connected.connect(self.check_login) self.connected.connect(self.check_login)
self.disconnected.connect(self.disconnect_from_host) self.disconnected.connect(self.disconnect_from_host)
self.error.connect(self.get_error) self.error.connect(self.get_error)
self.projectorReceivedData.connect(self._send_command)
def thread_stopped(self): def thread_stopped(self):
""" """
@ -608,6 +657,10 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands):
self.projectorReceivedData.disconnect(self._send_command) self.projectorReceivedData.disconnect(self._send_command)
except TypeError: except TypeError:
pass pass
try:
self.readyRead.disconnect(self.get_socket) # Set in process_pjlink
except TypeError:
pass
self.disconnect_from_host() self.disconnect_from_host()
self.deleteLater() self.deleteLater()
self.i_am_running = False self.i_am_running = False
@ -625,10 +678,10 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands):
Retrieve information from projector that changes. Retrieve information from projector that changes.
Normally called by timer(). Normally called by timer().
""" """
if self.state() != self.ConnectedState: if self.state() != S_QSOCKET_STATE['ConnectedState']:
log.warning("({ip}) poll_loop(): Not connected - returning".format(ip=self.ip)) log.warning('({ip}) poll_loop(): Not connected - returning'.format(ip=self.ip))
return return
log.debug('({ip}) Updating projector status'.format(ip=self.ip)) log.debug('({ip}) poll_loop(): Updating projector status'.format(ip=self.ip))
# Reset timer in case we were called from a set command # Reset timer in case we were called from a set command
if self.timer.interval() < self.poll_time: if self.timer.interval() < self.poll_time:
# Reset timer to 5 seconds # Reset timer to 5 seconds
@ -640,28 +693,28 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands):
if self.pjlink_class == '2': if self.pjlink_class == '2':
check_list.extend(['FILT', 'FREZ']) check_list.extend(['FILT', 'FREZ'])
for command in check_list: for command in check_list:
self.send_command(command, queue=True) self.send_command(command)
# The following commands do not change, so only check them once # The following commands do not change, so only check them once
if self.power == S_ON and self.source_available is None: if self.power == S_ON and self.source_available is None:
self.send_command('INST', queue=True) self.send_command('INST')
if self.other_info is None: if self.other_info is None:
self.send_command('INFO', queue=True) self.send_command('INFO')
if self.manufacturer is None: if self.manufacturer is None:
self.send_command('INF1', queue=True) self.send_command('INF1')
if self.model is None: if self.model is None:
self.send_command('INF2', queue=True) self.send_command('INF2')
if self.pjlink_name is None: if self.pjlink_name is None:
self.send_command('NAME', queue=True) self.send_command('NAME')
if self.pjlink_class == '2': if self.pjlink_class == '2':
# Class 2 specific checks # Class 2 specific checks
if self.serial_no is None: if self.serial_no is None:
self.send_command('SNUM', queue=True) self.send_command('SNUM')
if self.sw_version is None: if self.sw_version is None:
self.send_command('SVER', queue=True) self.send_command('SVER')
if self.model_filter is None: if self.model_filter is None:
self.send_command('RFIL', queue=True) self.send_command('RFIL')
if self.model_lamp is None: if self.model_lamp is None:
self.send_command('RLMP', queue=True) self.send_command('RLMP')
def _get_status(self, status): def _get_status(self, status):
""" """
@ -713,14 +766,12 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands):
code=status_code, code=status_code,
message=status_message if msg is None else msg)) message=status_message if msg is None else msg))
self.changeStatus.emit(self.ip, status, message) self.changeStatus.emit(self.ip, status, message)
self.projectorUpdateIcons.emit()
@QtCore.pyqtSlot() @QtCore.pyqtSlot()
def check_login(self, data=None): def check_login(self, data=None):
""" """
Processes the initial connection and authentication (if needed). Processes the initial connection and convert to a PJLink packet if valid initial connection
Starts poll timer if connection is established.
NOTE: Qt md5 hash function doesn't work with projector authentication. Use the python md5 hash function.
:param data: Optional data if called from another routine :param data: Optional data if called from another routine
""" """
@ -733,12 +784,12 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands):
self.change_status(E_SOCKET_TIMEOUT) self.change_status(E_SOCKET_TIMEOUT)
return return
read = self.readLine(self.max_size) read = self.readLine(self.max_size)
self.readLine(self.max_size) # Clean out the trailing \r\n self.readLine(self.max_size) # Clean out any trailing whitespace
if read is None: if read is None:
log.warning('({ip}) read is None - socket error?'.format(ip=self.ip)) log.warning('({ip}) read is None - socket error?'.format(ip=self.ip))
return return
elif len(read) < 8: elif len(read) < 8:
log.warning('({ip}) Not enough data read)'.format(ip=self.ip)) log.warning('({ip}) Not enough data read - skipping'.format(ip=self.ip))
return return
data = decode(read, 'utf-8') data = decode(read, 'utf-8')
# Possibility of extraneous data on input when reading. # Possibility of extraneous data on input when reading.
@ -750,9 +801,16 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands):
# PJLink initial login will be: # PJLink initial login will be:
# 'PJLink 0' - Unauthenticated login - no extra steps required. # 'PJLink 0' - Unauthenticated login - no extra steps required.
# 'PJLink 1 XXXXXX' Authenticated login - extra processing required. # 'PJLink 1 XXXXXX' Authenticated login - extra processing required.
if not data.upper().startswith('PJLINK'): if not data.startswith('PJLINK'):
# Invalid response # Invalid initial packet - close socket
log.error('({ip}) Invalid initial packet received - closing socket'.format(ip=self.ip))
return self.disconnect_from_host() return self.disconnect_from_host()
log.debug('({ip}) check_login(): Formatting initial connection prompt to PJLink packet'.format(ip=self.ip))
return self.get_data('{start}{clss}{data}'.format(start=PJLINK_PREFIX,
clss='1',
data=data.replace(' ', '=', 1)).encode('utf-8'))
# TODO: The below is replaced by process_pjlink() - remove when working properly
"""
if '=' in data: if '=' in data:
# Processing a login reply # Processing a login reply
data_check = data.strip().split('=') data_check = data.strip().split('=')
@ -801,18 +859,19 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands):
log.debug('({ip}) Starting timer'.format(ip=self.ip)) log.debug('({ip}) Starting timer'.format(ip=self.ip))
self.timer.setInterval(2000) # Set 2 seconds for initial information self.timer.setInterval(2000) # Set 2 seconds for initial information
self.timer.start() self.timer.start()
"""
def _trash_buffer(self, msg=None): def _trash_buffer(self, msg=None):
""" """
Clean out extraneous stuff in the buffer. Clean out extraneous stuff in the buffer.
""" """
log.warning("({ip}) {message}".format(ip=self.ip, message='Invalid packet' if msg is None else msg)) log.warning('({ip}) {message}'.format(ip=self.ip, message='Invalid packet' if msg is None else msg))
self.send_busy = False self.send_busy = False
trash_count = 0 trash_count = 0
while self.bytesAvailable() > 0: while self.bytesAvailable() > 0:
trash = self.read(self.max_size) trash = self.read(self.max_size)
trash_count += len(trash) trash_count += len(trash)
log.debug("({ip}) Finished cleaning buffer - {count} bytes dropped".format(ip=self.ip, log.debug('({ip}) Finished cleaning buffer - {count} bytes dropped'.format(ip=self.ip,
count=trash_count)) count=trash_count))
return return
@ -824,7 +883,7 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands):
:param data: Data to process. buffer must be formatted as a proper PJLink packet. :param data: Data to process. buffer must be formatted as a proper PJLink packet.
:param ip: Destination IP for buffer. :param ip: Destination IP for buffer.
""" """
log.debug("({ip}) get_buffer(data='{buff}' ip='{ip_in}'".format(ip=self.ip, buff=data, ip_in=ip)) log.debug('({ip}) get_buffer(data="{buff}" ip="{ip_in}"'.format(ip=self.ip, buff=data, ip_in=ip))
if ip is None: if ip is None:
log.debug("({ip}) get_buffer() Don't know who data is for - exiting".format(ip=self.ip)) log.debug("({ip}) get_buffer() Don't know who data is for - exiting".format(ip=self.ip))
return return
@ -842,38 +901,52 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands):
return return
# Although we have a packet length limit, go ahead and use a larger buffer # Although we have a packet length limit, go ahead and use a larger buffer
read = self.readLine(1024) read = self.readLine(1024)
log.debug("({ip}) get_socket(): '{buff}'".format(ip=self.ip, buff=read)) log.debug('({ip}) get_socket(): "{buff}"'.format(ip=self.ip, buff=read))
if read == -1: if read == -1:
# No data available # No data available
log.debug('({ip}) get_socket(): No data available (-1)'.format(ip=self.ip)) log.debug('({ip}) get_socket(): No data available (-1)'.format(ip=self.ip))
return self.receive_data_signal() return self.receive_data_signal()
self.socket_timer.stop() self.socket_timer.stop()
return self.get_data(buff=read, ip=self.ip) self.get_data(buff=read, ip=self.ip)
return self.receive_data_signal()
def get_data(self, buff, ip): def get_data(self, buff, ip=None):
""" """
Process received data Process received data
:param buff: Data to process. :param buff: Data to process.
:param ip: (optional) Destination IP. :param ip: (optional) Destination IP.
""" """
log.debug("({ip}) get_data(ip='{ip_in}' buffer='{buff}'".format(ip=self.ip, ip_in=ip, buff=buff)) # Since "self" is not available to options and the "ip" keyword is a "maybe I'll use in the future",
# set to default here
if ip is None:
ip = self.ip
log.debug('({ip}) get_data(ip="{ip_in}" buffer="{buff}"'.format(ip=self.ip, ip_in=ip, buff=buff))
# NOTE: Class2 has changed to some values being UTF-8 # NOTE: Class2 has changed to some values being UTF-8
data_in = decode(buff, 'utf-8') data_in = decode(buff, 'utf-8')
data = data_in.strip() data = data_in.strip()
if (len(data) < 7) or (not data.startswith(PJLINK_PREFIX)): # Initial packet checks
return self._trash_buffer(msg='get_data(): Invalid packet - length or prefix') if (len(data) < 7):
return self._trash_buffer(msg='get_data(): Invalid packet - length')
elif len(data) > self.max_size: elif len(data) > self.max_size:
return self._trash_buffer(msg='get_data(): Invalid packet - too long') return self._trash_buffer(msg='get_data(): Invalid packet - too long')
elif not data.startswith(PJLINK_PREFIX):
return self._trash_buffer(msg='get_data(): Invalid packet - PJLink prefix missing')
elif '=' not in data: elif '=' not in data:
return self._trash_buffer(msg='get_data(): Invalid packet does not have equal') return self._trash_buffer(msg='get_data(): Invalid reply - Does not have "="')
log.debug('({ip}) get_data(): Checking new data "{data}"'.format(ip=self.ip, data=data)) log.debug('({ip}) get_data(): Checking new data "{data}"'.format(ip=self.ip, data=data))
header, data = data.split('=') header, data = data.split('=')
# At this point, the header should contain:
# "PVCCCC"
# Where:
# P = PJLINK_PREFIX
# V = PJLink class or version
# C = PJLink command
try: try:
version, cmd = header[1], header[2:] version, cmd = header[1], header[2:].upper()
except ValueError as e: except ValueError as e:
self.change_status(E_INVALID_DATA) self.change_status(E_INVALID_DATA)
log.warning('({ip}) get_data(): Received data: "{data}"'.format(ip=self.ip, data=data_in.strip())) log.warning('({ip}) get_data(): Received data: "{data}"'.format(ip=self.ip, data=data_in))
return self._trash_buffer('get_data(): Expected header + command + data') return self._trash_buffer('get_data(): Expected header + command + data')
if cmd not in PJLINK_VALID_CMD: if cmd not in PJLINK_VALID_CMD:
log.warning('({ip}) get_data(): Invalid packet - unknown command "{data}"'.format(ip=self.ip, data=cmd)) log.warning('({ip}) get_data(): Invalid packet - unknown command "{data}"'.format(ip=self.ip, data=cmd))
@ -881,6 +954,7 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands):
if int(self.pjlink_class) < int(version): if int(self.pjlink_class) < int(version):
log.warning('({ip}) get_data(): Projector returned class reply higher ' log.warning('({ip}) get_data(): Projector returned class reply higher '
'than projector stated class'.format(ip=self.ip)) 'than projector stated class'.format(ip=self.ip))
self.send_busy = False
return self.process_command(cmd, data) return self.process_command(cmd, data)
@QtCore.pyqtSlot(QtNetwork.QAbstractSocket.SocketError) @QtCore.pyqtSlot(QtNetwork.QAbstractSocket.SocketError)
@ -910,19 +984,18 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands):
self.reset_information() self.reset_information()
return return
def send_command(self, cmd, opts='?', salt=None, queue=False): def send_command(self, cmd, opts='?', salt=None, priority=False):
""" """
Add command to output queue if not already in queue. Add command to output queue if not already in queue.
:param cmd: Command to send :param cmd: Command to send
:param opts: Command option (if any) - defaults to '?' (get information) :param opts: Command option (if any) - defaults to '?' (get information)
:param salt: Optional salt for md5 hash initial authentication :param salt: Optional salt for md5 hash initial authentication
:param queue: Option to force add to queue rather than sending directly :param priority: Option to send packet now rather than queue it up
""" """
if self.state() != self.ConnectedState: if self.state() != self.ConnectedState:
log.warning('({ip}) send_command(): Not connected - returning'.format(ip=self.ip)) log.warning('({ip}) send_command(): Not connected - returning'.format(ip=self.ip))
self.send_queue = [] return self.reset_information()
return
if cmd not in PJLINK_VALID_CMD: if cmd not in PJLINK_VALID_CMD:
log.error('({ip}) send_command(): Invalid command requested - ignoring.'.format(ip=self.ip)) log.error('({ip}) send_command(): Invalid command requested - ignoring.'.format(ip=self.ip))
return return
@ -939,28 +1012,26 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands):
header = PJLINK_HEADER.format(linkclass=cmd_ver[0]) header = PJLINK_HEADER.format(linkclass=cmd_ver[0])
else: else:
# NOTE: Once we get to version 3 then think about looping # NOTE: Once we get to version 3 then think about looping
log.error('({ip}): send_command(): PJLink class check issue? aborting'.format(ip=self.ip)) log.error('({ip}): send_command(): PJLink class check issue? Aborting'.format(ip=self.ip))
return return
out = '{salt}{header}{command} {options}{suffix}'.format(salt="" if salt is None else salt, out = '{salt}{header}{command} {options}{suffix}'.format(salt="" if salt is None else salt,
header=header, header=header,
command=cmd, command=cmd,
options=opts, options=opts,
suffix=CR) suffix=CR)
if out in self.send_queue: if out in self.priority_queue:
# Already there, so don't add log.debug('({ip}) send_command(): Already in priority queue - skipping'.format(ip=self.ip))
log.debug('({ip}) send_command(out="{data}") Already in queue - skipping'.format(ip=self.ip, elif out in self.send_queue:
data=out.strip())) log.debug('({ip}) send_command(): Already in normal queue - skipping'.format(ip=self.ip))
elif not queue and len(self.send_queue) == 0:
# Nothing waiting to send, so just send it
log.debug('({ip}) send_command(out="{data}") Sending data'.format(ip=self.ip, data=out.strip()))
return self._send_command(data=out)
else: else:
log.debug('({ip}) send_command(out="{data}") adding to queue'.format(ip=self.ip, data=out.strip())) if priority:
self.send_queue.append(out) log.debug('({ip}) send_command(): Adding to priority queue'.format(ip=self.ip))
self.projectorReceivedData.emit() self.priority_queue.append(out)
log.debug('({ip}) send_command(): send_busy is {data}'.format(ip=self.ip, data=self.send_busy)) else:
if not self.send_busy: log.debug('({ip}) send_command(): Adding to normal queue'.format(ip=self.ip))
log.debug('({ip}) send_command() calling _send_string()'.format(ip=self.ip)) self.send_queue.append(out)
if self.priority_queue or self.send_queue:
# May be some initial connection setup so make sure we send data
self._send_command() self._send_command()
@QtCore.pyqtSlot() @QtCore.pyqtSlot()
@ -971,43 +1042,53 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands):
:param data: Immediate data to send :param data: Immediate data to send
:param utf8: Send as UTF-8 string otherwise send as ASCII string :param utf8: Send as UTF-8 string otherwise send as ASCII string
""" """
log.debug('({ip}) _send_string()'.format(ip=self.ip)) # Funny looking data check, but it's a quick check for data=None
log.debug('({ip}) _send_string(): Connection status: {data}'.format(ip=self.ip, data=self.state())) log.debug('({ip}) _send_command(data="{data}")'.format(ip=self.ip, data=data.strip() if data else data))
log.debug('({ip}) _send_command(): Connection status: {data}'.format(ip=self.ip,
data=S_QSOCKET_STATE[self.state()]))
if self.state() != self.ConnectedState: if self.state() != self.ConnectedState:
log.debug('({ip}) _send_string() Not connected - abort'.format(ip=self.ip)) log.debug('({ip}) _send_command() Not connected - abort'.format(ip=self.ip))
self.send_queue = []
self.send_busy = False self.send_busy = False
return return self.disconnect_from_host()
if data and data not in self.priority_queue:
log.debug('({ip}) _send_command(): Priority packet - adding to priority queue'.format(ip=self.ip))
self.priority_queue.append(data)
if self.send_busy: if self.send_busy:
# Still waiting for response from last command sent # Still waiting for response from last command sent
log.debug('({ip}) _send_command(): Still busy, returning'.format(ip=self.ip))
log.debug('({ip}) _send_command(): Priority queue = {data}'.format(ip=self.ip, data=self.priority_queue))
log.debug('({ip}) _send_command(): Normal queue = {data}'.format(ip=self.ip, data=self.send_queue))
return return
if data is not None:
out = data if len(self.priority_queue) != 0:
log.debug('({ip}) _send_string(data="{data}")'.format(ip=self.ip, data=out.strip())) out = self.priority_queue.pop(0)
log.debug('({ip}) _send_command(): Getting priority queued packet'.format(ip=self.ip))
elif len(self.send_queue) != 0: elif len(self.send_queue) != 0:
out = self.send_queue.pop(0) out = self.send_queue.pop(0)
log.debug('({ip}) _send_string(queued data="{data}"%s)'.format(ip=self.ip, data=out.strip())) log.debug('({ip}) _send_command(): Getting normal queued packet'.format(ip=self.ip))
else: else:
# No data to send # No data to send
log.debug('({ip}) _send_string(): No data to send'.format(ip=self.ip)) log.debug('({ip}) _send_command(): No data to send'.format(ip=self.ip))
self.send_busy = False self.send_busy = False
return return
self.send_busy = True self.send_busy = True
log.debug('({ip}) _send_string(): Sending "{data}"'.format(ip=self.ip, data=out.strip())) log.debug('({ip}) _send_command(): Sending "{data}"'.format(ip=self.ip, data=out.strip()))
log.debug('({ip}) _send_string(): Queue = {data}'.format(ip=self.ip, data=self.send_queue))
self.socket_timer.start() self.socket_timer.start()
sent = self.write(out.encode('{string_encoding}'.format(string_encoding='utf-8' if utf8 else 'ascii'))) sent = self.write(out.encode('{string_encoding}'.format(string_encoding='utf-8' if utf8 else 'ascii')))
self.waitForBytesWritten(2000) # 2 seconds should be enough self.waitForBytesWritten(2000) # 2 seconds should be enough
if sent == -1: if sent == -1:
# Network error? # Network error?
log.warning("({ip}) _send_command(): -1 received".format(ip=self.ip)) log.warning('({ip}) _send_command(): -1 received - disconnecting from host'.format(ip=self.ip))
self.change_status(E_NETWORK, self.change_status(E_NETWORK,
translate('OpenLP.PJLink', 'Error while sending data to projector')) translate('OpenLP.PJLink', 'Error while sending data to projector'))
self.disconnect_from_host()
def connect_to_host(self): def connect_to_host(self):
""" """
Initiate connection to projector. Initiate connection to projector.
""" """
log.debug('{ip}) connect_to_host(): Starting connection'.format(ip=self.ip))
if self.state() == self.ConnectedState: if self.state() == self.ConnectedState:
log.warning('({ip}) connect_to_host(): Already connected - returning'.format(ip=self.ip)) log.warning('({ip}) connect_to_host(): Already connected - returning'.format(ip=self.ip))
return return
@ -1023,22 +1104,19 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands):
if abort: if abort:
log.warning('({ip}) disconnect_from_host(): Aborting connection'.format(ip=self.ip)) log.warning('({ip}) disconnect_from_host(): Aborting connection'.format(ip=self.ip))
else: else:
log.warning('({ip}) disconnect_from_host(): Not connected - returning'.format(ip=self.ip)) log.warning('({ip}) disconnect_from_host(): Not connected'.format(ip=self.ip))
self.reset_information()
self.disconnectFromHost() self.disconnectFromHost()
try: try:
self.readyRead.disconnect(self.get_socket) self.readyRead.disconnect(self.get_socket)
except TypeError: except TypeError:
pass pass
log.debug('({ip}) disconnect_from_host() '
'Current status {data}'.format(ip=self.ip, data=self._get_status(self.status_connect)[0]))
if abort: if abort:
self.change_status(E_NOT_CONNECTED) self.change_status(E_NOT_CONNECTED)
else: else:
log.debug('({ip}) disconnect_from_host() ' self.change_status(S_NOT_CONNECTED)
'Current status {data}'.format(ip=self.ip, data=self._get_status(self.status_connect)[0]))
if self.status_connect != E_NOT_CONNECTED:
self.change_status(S_NOT_CONNECTED)
self.reset_information() self.reset_information()
self.projectorUpdateIcons.emit()
def get_av_mute_status(self): def get_av_mute_status(self):
""" """

View File

@ -504,9 +504,8 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
Settings().set_up_default_values() Settings().set_up_default_values()
self.about_form = AboutForm(self) self.about_form = AboutForm(self)
MediaController() MediaController()
if Registry().get_flag('no_web_server'): websockets.WebSocketServer()
websockets.WebSocketServer() server.HttpServer()
server.HttpServer()
SettingsForm(self) SettingsForm(self)
self.formatting_tag_form = FormattingTagForm(self) self.formatting_tag_form = FormattingTagForm(self)
self.shortcut_form = ShortcutListForm(self) self.shortcut_form = ShortcutListForm(self)

View File

@ -498,8 +498,6 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
:param controller: The media controller. :param controller: The media controller.
:return: True if setup succeeded else False. :return: True if setup succeeded else False.
""" """
if controller is None:
controller = self.display_controllers[DisplayControllerType.Plugin]
# stop running videos # stop running videos
self.media_reset(controller) self.media_reset(controller)
# Setup media info # Setup media info
@ -509,9 +507,9 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
controller.media_info.media_type = MediaType.CD controller.media_info.media_type = MediaType.CD
else: else:
controller.media_info.media_type = MediaType.DVD controller.media_info.media_type = MediaType.DVD
controller.media_info.start_time = start // 1000 controller.media_info.start_time = start
controller.media_info.end_time = end // 1000 controller.media_info.end_time = end
controller.media_info.length = (end - start) // 1000 controller.media_info.length = (end - start)
controller.media_info.title_track = title controller.media_info.title_track = title
controller.media_info.audio_track = audio_track controller.media_info.audio_track = audio_track
controller.media_info.subtitle_track = subtitle_track controller.media_info.subtitle_track = subtitle_track

View File

@ -280,7 +280,8 @@ class VlcPlayer(MediaPlayer):
start_time = controller.media_info.start_time start_time = controller.media_info.start_time
log.debug('mediatype: ' + str(controller.media_info.media_type)) log.debug('mediatype: ' + str(controller.media_info.media_type))
# Set tracks for the optical device # Set tracks for the optical device
if controller.media_info.media_type == MediaType.DVD: if controller.media_info.media_type == MediaType.DVD and \
self.get_live_state() != MediaState.Paused and self.get_preview_state() != MediaState.Paused:
log.debug('vlc play, playing started') log.debug('vlc play, playing started')
if controller.media_info.title_track > 0: if controller.media_info.title_track > 0:
log.debug('vlc play, title_track set: ' + str(controller.media_info.title_track)) log.debug('vlc play, title_track set: ' + str(controller.media_info.title_track))
@ -350,7 +351,7 @@ class VlcPlayer(MediaPlayer):
""" """
if display.controller.media_info.media_type == MediaType.CD \ if display.controller.media_info.media_type == MediaType.CD \
or display.controller.media_info.media_type == MediaType.DVD: or display.controller.media_info.media_type == MediaType.DVD:
seek_value += int(display.controller.media_info.start_time * 1000) seek_value += int(display.controller.media_info.start_time)
if display.vlc_media_player.is_seekable(): if display.vlc_media_player.is_seekable():
display.vlc_media_player.set_time(seek_value) display.vlc_media_player.set_time(seek_value)
@ -386,15 +387,15 @@ class VlcPlayer(MediaPlayer):
self.stop(display) self.stop(display)
controller = display.controller controller = display.controller
if controller.media_info.end_time > 0: if controller.media_info.end_time > 0:
if display.vlc_media_player.get_time() > controller.media_info.end_time * 1000: if display.vlc_media_player.get_time() > controller.media_info.end_time:
self.stop(display) self.stop(display)
self.set_visible(display, False) self.set_visible(display, False)
if not controller.seek_slider.isSliderDown(): if not controller.seek_slider.isSliderDown():
controller.seek_slider.blockSignals(True) controller.seek_slider.blockSignals(True)
if display.controller.media_info.media_type == MediaType.CD \ if display.controller.media_info.media_type == MediaType.CD \
or display.controller.media_info.media_type == MediaType.DVD: or display.controller.media_info.media_type == MediaType.DVD:
controller.seek_slider.setSliderPosition(display.vlc_media_player.get_time() - controller.seek_slider.setSliderPosition(
int(display.controller.media_info.start_time * 1000)) display.vlc_media_player.get_time() - int(display.controller.media_info.start_time))
else: else:
controller.seek_slider.setSliderPosition(display.vlc_media_player.get_time()) controller.seek_slider.setSliderPosition(display.vlc_media_player.get_time())
controller.seek_slider.blockSignals(False) controller.seek_slider.blockSignals(False)

View File

@ -350,7 +350,10 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi
if modified: if modified:
self.service_id += 1 self.service_id += 1
self._modified = modified self._modified = modified
service_file = self.short_file_name() or translate('OpenLP.ServiceManager', 'Untitled Service') if self._service_path:
service_file = self._service_path.name
else:
service_file = translate('OpenLP.ServiceManager', 'Untitled Service')
self.main_window.set_service_modified(modified, service_file) self.main_window.set_service_modified(modified, service_file)
def is_modified(self): def is_modified(self):
@ -367,7 +370,7 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi
:rtype: None :rtype: None
""" """
self._service_path = file_path self._service_path = file_path
self.main_window.set_service_modified(self.is_modified(), self.short_file_name()) self.main_window.set_service_modified(self.is_modified(), file_path.name)
Settings().setValue('servicemanager/last file', file_path) Settings().setValue('servicemanager/last file', file_path)
if file_path and file_path.suffix == '.oszl': if file_path and file_path.suffix == '.oszl':
self._save_lite = True self._save_lite = True
@ -386,7 +389,8 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi
""" """
Return the current file name, excluding the path. Return the current file name, excluding the path.
""" """
return self._service_path.name if self._service_path:
return self._service_path.name
def reset_supported_suffixes(self): def reset_supported_suffixes(self):
""" """

View File

@ -28,6 +28,7 @@ from datetime import datetime
from PyQt5 import QtCore, QtGui, QtWidgets from PyQt5 import QtCore, QtGui, QtWidgets
from openlp.core.common import is_win, is_linux, is_macosx from openlp.core.common import is_win, is_linux, is_macosx
from openlp.core.common.path import Path
from openlp.core.common.i18n import translate from openlp.core.common.i18n import translate
from openlp.core.common.mixins import RegistryProperties from openlp.core.common.mixins import RegistryProperties
from openlp.plugins.media.forms.mediaclipselectordialog import Ui_MediaClipSelector from openlp.plugins.media.forms.mediaclipselectordialog import Ui_MediaClipSelector
@ -109,7 +110,7 @@ class MediaClipSelectorForm(QtWidgets.QDialog, Ui_MediaClipSelector, RegistryPro
self.subtitle_tracks_combobox.clear() self.subtitle_tracks_combobox.clear()
self.audio_tracks_combobox.clear() self.audio_tracks_combobox.clear()
self.titles_combo_box.clear() self.titles_combo_box.clear()
time = QtCore.QTime() time = QtCore.QTime(0, 0, 0)
self.start_position_edit.setTime(time) self.start_position_edit.setTime(time)
self.end_timeedit.setTime(time) self.end_timeedit.setTime(time)
self.position_timeedit.setTime(time) self.position_timeedit.setTime(time)
@ -294,7 +295,7 @@ class MediaClipSelectorForm(QtWidgets.QDialog, Ui_MediaClipSelector, RegistryPro
:param clicked: Given from signal, not used. :param clicked: Given from signal, not used.
""" """
vlc_ms_pos = self.vlc_media_player.get_time() vlc_ms_pos = self.vlc_media_player.get_time()
time = QtCore.QTime() time = QtCore.QTime(0, 0, 0)
new_pos_time = time.addMSecs(vlc_ms_pos) new_pos_time = time.addMSecs(vlc_ms_pos)
self.start_position_edit.setTime(new_pos_time) self.start_position_edit.setTime(new_pos_time)
# If start time is after end time, update end time. # If start time is after end time, update end time.
@ -310,7 +311,7 @@ class MediaClipSelectorForm(QtWidgets.QDialog, Ui_MediaClipSelector, RegistryPro
:param clicked: Given from signal, not used. :param clicked: Given from signal, not used.
""" """
vlc_ms_pos = self.vlc_media_player.get_time() vlc_ms_pos = self.vlc_media_player.get_time()
time = QtCore.QTime() time = QtCore.QTime(0, 0, 0)
new_pos_time = time.addMSecs(vlc_ms_pos) new_pos_time = time.addMSecs(vlc_ms_pos)
self.end_timeedit.setTime(new_pos_time) self.end_timeedit.setTime(new_pos_time)
# If start time is after end time, update start time. # If start time is after end time, update start time.
@ -447,7 +448,7 @@ class MediaClipSelectorForm(QtWidgets.QDialog, Ui_MediaClipSelector, RegistryPro
self.position_slider.setMaximum(self.playback_length) self.position_slider.setMaximum(self.playback_length)
# setup start and end time # setup start and end time
rounded_vlc_ms_length = int(round(self.playback_length / 100.0) * 100.0) rounded_vlc_ms_length = int(round(self.playback_length / 100.0) * 100.0)
time = QtCore.QTime() time = QtCore.QTime(0, 0, 0)
playback_length_time = time.addMSecs(rounded_vlc_ms_length) playback_length_time = time.addMSecs(rounded_vlc_ms_length)
self.start_position_edit.setMaximumTime(playback_length_time) self.start_position_edit.setMaximumTime(playback_length_time)
self.end_timeedit.setMaximumTime(playback_length_time) self.end_timeedit.setMaximumTime(playback_length_time)
@ -505,7 +506,7 @@ class MediaClipSelectorForm(QtWidgets.QDialog, Ui_MediaClipSelector, RegistryPro
if self.vlc_media_player: if self.vlc_media_player:
vlc_ms_pos = self.vlc_media_player.get_time() vlc_ms_pos = self.vlc_media_player.get_time()
rounded_vlc_ms_pos = int(round(vlc_ms_pos / 100.0) * 100.0) rounded_vlc_ms_pos = int(round(vlc_ms_pos / 100.0) * 100.0)
time = QtCore.QTime() time = QtCore.QTime(0, 0, 0)
new_pos_time = time.addMSecs(rounded_vlc_ms_pos) new_pos_time = time.addMSecs(rounded_vlc_ms_pos)
self.position_timeedit.setTime(new_pos_time) self.position_timeedit.setTime(new_pos_time)
self.position_slider.setSliderPosition(vlc_ms_pos) self.position_slider.setSliderPosition(vlc_ms_pos)
@ -615,7 +616,7 @@ class MediaClipSelectorForm(QtWidgets.QDialog, Ui_MediaClipSelector, RegistryPro
break break
# Append the new name to the optical string and the path # Append the new name to the optical string and the path
optical += new_optical_name + ':' + path optical += new_optical_name + ':' + path
self.media_item.add_optical_clip(optical) self.media_item.add_optical_clip(Path(optical))
def media_state_wait(self, media_state): def media_state_wait(self, media_state):
""" """

View File

@ -269,10 +269,9 @@ class MediaMediaItem(MediaManagerItem, RegistryProperties):
service_item.add_from_command(filename, name, CLAPPERBOARD) service_item.add_from_command(filename, name, CLAPPERBOARD)
service_item.title = clip_name service_item.title = clip_name
# Set the length # Set the length
self.media_controller.media_setup_optical(name, title, audio_track, subtitle_track, start, end, None, None) service_item.set_media_length(end - start)
service_item.set_media_length((end - start) / 1000) service_item.start_time = start
service_item.start_time = start / 1000 service_item.end_time = end
service_item.end_time = end / 1000
service_item.add_capability(ItemCapabilities.IsOptical) service_item.add_capability(ItemCapabilities.IsOptical)
else: else:
if not os.path.exists(filename): if not os.path.exists(filename):
@ -455,5 +454,5 @@ class MediaMediaItem(MediaManagerItem, RegistryProperties):
return return
# Append the optical string to the media list # Append the optical string to the media list
file_paths.append(optical) file_paths.append(optical)
self.load_list([optical]) self.load_list([str(optical)])
Settings().setValue(self.settings_section + '/media files', file_paths) Settings().setValue(self.settings_section + '/media files', file_paths)

View File

@ -63,9 +63,10 @@ class OpenLPJobs(object):
Branch_Coverage = 'Branch-04b-Test_Coverage' Branch_Coverage = 'Branch-04b-Test_Coverage'
Branch_Pylint = 'Branch-04c-Code_Analysis2' Branch_Pylint = 'Branch-04c-Code_Analysis2'
Branch_AppVeyor = 'Branch-05-AppVeyor-Tests' Branch_AppVeyor = 'Branch-05-AppVeyor-Tests'
Branch_macOS = 'Branch-07-macOS-Tests'
Jobs = [Branch_Pull, Branch_Functional, Branch_Interface, Branch_PEP, Branch_Coverage, Branch_Pylint, Jobs = [Branch_Pull, Branch_Functional, Branch_Interface, Branch_PEP, Branch_Coverage, Branch_Pylint,
Branch_AppVeyor] Branch_AppVeyor, Branch_macOS]
class Colour(object): class Colour(object):
@ -115,7 +116,7 @@ class JenkinsTrigger(object):
self.fetch_jobs() self.fetch_jobs()
self.server.build_job(OpenLPJobs.Branch_Pull, {'BRANCH_NAME': self.repo_name, 'cause': cause}) self.server.build_job(OpenLPJobs.Branch_Pull, {'BRANCH_NAME': self.repo_name, 'cause': cause})
def print_output(self): def print_output(self, can_continue=False):
""" """
Print the status information of the build triggered. Print the status information of the build triggered.
""" """
@ -126,13 +127,21 @@ class JenkinsTrigger(object):
revno = raw_output.decode().strip() revno = raw_output.decode().strip()
print('%s (revision %s)' % (get_repo_name(), revno)) print('%s (revision %s)' % (get_repo_name(), revno))
failed_builds = []
for job in OpenLPJobs.Jobs: for job in OpenLPJobs.Jobs:
if not self.__print_build_info(job): if not self.__print_build_info(job):
if self.current_build: if self.current_build:
print('Stopping after failure, see {}console for more details'.format(self.current_build['url'])) failed_builds.append((self.current_build['fullDisplayName'], self.current_build['url']))
else: if not can_continue:
print('Stopping after failure') print('Stopping after failure')
break break
print('')
if failed_builds:
print('Failed builds:')
for build_name, url in failed_builds:
print(' - {}: {}console'.format(build_name, url))
else:
print('All builds passed')
def open_browser(self): def open_browser(self):
""" """
@ -227,6 +236,7 @@ def main():
help='Disable coloured output (always disabled on Windows)') help='Disable coloured output (always disabled on Windows)')
parser.add_argument('-u', '--username', required=True, help='Your Jenkins username') parser.add_argument('-u', '--username', required=True, help='Your Jenkins username')
parser.add_argument('-p', '--password', required=True, help='Your Jenkins password or personal token') parser.add_argument('-p', '--password', required=True, help='Your Jenkins password or personal token')
parser.add_argument('-c', '--always-continue', action='store_true', default=False, help='Continue despite failure')
args = parser.parse_args() args = parser.parse_args()
if not get_repo_name(): if not get_repo_name():
@ -238,7 +248,7 @@ def main():
if args.open_browser: if args.open_browser:
jenkins_trigger.open_browser() jenkins_trigger.open_browser()
if not args.disable_output: if not args.disable_output:
jenkins_trigger.print_output() jenkins_trigger.print_output(can_continue=args.always_continue)
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -45,12 +45,28 @@ class TestHttpServer(TestCase):
@patch('openlp.core.api.http.server.QtCore.QThread') @patch('openlp.core.api.http.server.QtCore.QThread')
def test_server_start(self, mock_qthread, mock_thread): def test_server_start(self, mock_qthread, mock_thread):
""" """
Test the starting of the Waitress Server Test the starting of the Waitress Server with the disable flag set off
""" """
# GIVEN: A new httpserver # GIVEN: A new httpserver
# WHEN: I start the server # WHEN: I start the server
Registry().set_flag('no_web_server', True)
HttpServer() HttpServer()
# THEN: the api environment should have been created # THEN: the api environment should have been created
self.assertEquals(1, mock_qthread.call_count, 'The qthread should have been called once') self.assertEquals(1, mock_qthread.call_count, 'The qthread should have been called once')
self.assertEquals(1, mock_thread.call_count, 'The http thread should have been called once') self.assertEquals(1, mock_thread.call_count, 'The http thread should have been called once')
@patch('openlp.core.api.http.server.HttpWorker')
@patch('openlp.core.api.http.server.QtCore.QThread')
def test_server_start_not_required(self, mock_qthread, mock_thread):
"""
Test the starting of the Waitress Server with the disable flag set off
"""
# GIVEN: A new httpserver
# WHEN: I start the server
Registry().set_flag('no_web_server', False)
HttpServer()
# THEN: the api environment should have been created
self.assertEquals(0, mock_qthread.call_count, 'The qthread should not have have been called')
self.assertEquals(0, mock_thread.call_count, 'The http thread should not have been called')

View File

@ -66,16 +66,32 @@ class TestWSServer(TestCase, TestMixin):
@patch('openlp.core.api.websockets.QtCore.QThread') @patch('openlp.core.api.websockets.QtCore.QThread')
def test_serverstart(self, mock_qthread, mock_worker): def test_serverstart(self, mock_qthread, mock_worker):
""" """
Test the starting of the WebSockets Server Test the starting of the WebSockets Server with the disabled flag set on
""" """
# GIVEN: A new httpserver # GIVEN: A new httpserver
# WHEN: I start the server # WHEN: I start the server
Registry().set_flag('no_web_server', True)
WebSocketServer() WebSocketServer()
# THEN: the api environment should have been created # THEN: the api environment should have been created
self.assertEquals(1, mock_qthread.call_count, 'The qthread should have been called once') self.assertEquals(1, mock_qthread.call_count, 'The qthread should have been called once')
self.assertEquals(1, mock_worker.call_count, 'The http thread should have been called once') self.assertEquals(1, mock_worker.call_count, 'The http thread should have been called once')
@patch('openlp.core.api.websockets.WebSocketWorker')
@patch('openlp.core.api.websockets.QtCore.QThread')
def test_serverstart_not_required(self, mock_qthread, mock_worker):
"""
Test the starting of the WebSockets Server with the disabled flag set off
"""
# GIVEN: A new httpserver and the server is not required
# WHEN: I start the server
Registry().set_flag('no_web_server', False)
WebSocketServer()
# THEN: the api environment should have been created
self.assertEquals(0, mock_qthread.call_count, 'The qthread should not have been called')
self.assertEquals(0, mock_worker.call_count, 'The http thread should not have been called')
def test_main_poll(self): def test_main_poll(self):
""" """
Test the main_poll function returns the correct JSON Test the main_poll function returns the correct JSON

View File

@ -22,8 +22,10 @@
""" """
Package to test the openlp.core.lib.languages package. Package to test the openlp.core.lib.languages package.
""" """
from unittest import skipIf
from unittest.mock import MagicMock, patch from unittest.mock import MagicMock, patch
from openlp.core.common import is_macosx
from openlp.core.common.i18n import LANGUAGES, Language, UiStrings, get_language, get_locale_key, get_natural_key, \ from openlp.core.common.i18n import LANGUAGES, Language, UiStrings, get_language, get_locale_key, get_natural_key, \
translate translate
@ -110,6 +112,7 @@ def test_get_language_invalid_with_none():
assert language is None assert language is None
@skipIf(is_macosx(), 'This test doesn\'t work on macOS currently')
def test_get_locale_key(): def test_get_locale_key():
""" """
Test the get_locale_key(string) function Test the get_locale_key(string) function

View File

@ -0,0 +1,45 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2017 OpenLP Developers #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
Package to test the openlp.core.lib.exceptions package.
"""
from unittest import TestCase
from openlp.core.lib.exceptions import ValidationError
class TestValidationError(TestCase):
"""
Test the ValidationError Class
"""
def test_validation_error(self):
"""
Test the creation of a ValidationError
"""
# GIVEN: The ValidationError class
# WHEN: Creating an instance of ValidationError
error = ValidationError('Test ValidationError')
# THEN: Then calling str on the error should return the correct text and it should be an instance of `Exception`
assert str(error) == 'Test ValidationError'
assert isinstance(error, Exception)

View File

@ -42,8 +42,8 @@ class TestMediaManagerItem(TestCase, TestMixin):
self.mocked_setup = self.setup_patcher.start() self.mocked_setup = self.setup_patcher.start()
self.addCleanup(self.setup_patcher.stop) self.addCleanup(self.setup_patcher.stop)
@patch(u'openlp.core.lib.mediamanageritem.Settings') @patch('openlp.core.lib.mediamanageritem.Settings')
@patch(u'openlp.core.lib.mediamanageritem.MediaManagerItem.on_preview_click') @patch('openlp.core.lib.mediamanageritem.MediaManagerItem.on_preview_click')
def test_on_double_clicked(self, mocked_on_preview_click, MockedSettings): def test_on_double_clicked(self, mocked_on_preview_click, MockedSettings):
""" """
Test that when an item is double-clicked then the item is previewed Test that when an item is double-clicked then the item is previewed
@ -75,8 +75,8 @@ class TestMediaManagerItem(TestCase, TestMixin):
self.assertTrue(mmi.has_delete_icon, 'By default a delete icon should be present') self.assertTrue(mmi.has_delete_icon, 'By default a delete icon should be present')
self.assertFalse(mmi.add_to_service_item, 'There should be no add_to_service icon by default') self.assertFalse(mmi.add_to_service_item, 'There should be no add_to_service icon by default')
@patch(u'openlp.core.lib.mediamanageritem.Settings') @patch('openlp.core.lib.mediamanageritem.Settings')
@patch(u'openlp.core.lib.mediamanageritem.MediaManagerItem.on_live_click') @patch('openlp.core.lib.mediamanageritem.MediaManagerItem.on_live_click')
def test_on_double_clicked_go_live(self, mocked_on_live_click, MockedSettings): def test_on_double_clicked_go_live(self, mocked_on_live_click, MockedSettings):
""" """
Test that when "Double-click to go live" is enabled that the item goes live Test that when "Double-click to go live" is enabled that the item goes live
@ -93,9 +93,9 @@ class TestMediaManagerItem(TestCase, TestMixin):
# THEN: on_live_click() should have been called # THEN: on_live_click() should have been called
mocked_on_live_click.assert_called_with() mocked_on_live_click.assert_called_with()
@patch(u'openlp.core.lib.mediamanageritem.Settings') @patch('openlp.core.lib.mediamanageritem.Settings')
@patch(u'openlp.core.lib.mediamanageritem.MediaManagerItem.on_live_click') @patch('openlp.core.lib.mediamanageritem.MediaManagerItem.on_live_click')
@patch(u'openlp.core.lib.mediamanageritem.MediaManagerItem.on_preview_click') @patch('openlp.core.lib.mediamanageritem.MediaManagerItem.on_preview_click')
def test_on_double_clicked_single_click_preview(self, mocked_on_preview_click, mocked_on_live_click, def test_on_double_clicked_single_click_preview(self, mocked_on_preview_click, mocked_on_live_click,
MockedSettings): MockedSettings):
""" """
@ -111,5 +111,5 @@ class TestMediaManagerItem(TestCase, TestMixin):
mmi.on_double_clicked() mmi.on_double_clicked()
# THEN: on_live_click() should have been called # THEN: on_live_click() should have been called
self.assertEqual(0, mocked_on_live_click.call_count, u'on_live_click() should not have been called') self.assertEqual(0, mocked_on_live_click.call_count, 'on_live_click() should not have been called')
self.assertEqual(0, mocked_on_preview_click.call_count, u'on_preview_click() should not have been called') self.assertEqual(0, mocked_on_preview_click.call_count, 'on_preview_click() should not have been called')

View File

@ -23,12 +23,11 @@
Package to test the openlp.core.projectors.pjlink base package. Package to test the openlp.core.projectors.pjlink base package.
""" """
from unittest import TestCase from unittest import TestCase
from unittest.mock import patch
from openlp.core.projectors.db import Projector from openlp.core.projectors.db import Projector
from openlp.core.projectors.pjlink import PJLink from openlp.core.projectors.pjlink import PJLink
from tests.resources.projector.data import TEST_PIN, TEST_CONNECT_AUTHENTICATE, TEST_HASH, TEST1_DATA from tests.resources.projector.data import TEST1_DATA
class TestPJLinkBugs(TestCase): class TestPJLinkBugs(TestCase):
@ -80,43 +79,17 @@ class TestPJLinkBugs(TestCase):
""" """
Test bug 1593882 no pin and authenticated request exception Test bug 1593882 no pin and authenticated request exception
""" """
# GIVEN: Test object and mocks # Test now part of test_projector_pjlink_commands_02
mock_socket_timer = patch.object(self.pjlink_test, 'socket_timer').start() # Keeping here for bug reference
mock_timer = patch.object(self.pjlink_test, 'timer').start() pass
mock_authentication = patch.object(self.pjlink_test, 'projectorAuthentication').start()
mock_ready_read = patch.object(self.pjlink_test, 'waitForReadyRead').start()
mock_send_command = patch.object(self.pjlink_test, 'send_command').start()
pjlink = self.pjlink_test
pjlink.pin = None
mock_ready_read.return_value = True
# WHEN: call with authentication request and pin not set
pjlink.check_login(data=TEST_CONNECT_AUTHENTICATE)
# THEN: 'No Authentication' signal should have been sent
mock_authentication.emit.assert_called_with(pjlink.ip)
def test_bug_1593883_pjlink_authentication(self): def test_bug_1593883_pjlink_authentication(self):
""" """
Test bugfix 1593883 pjlink authentication Test bugfix 1593883 pjlink authentication and ticket 92187
""" """
# GIVEN: Test object and data # Test now part of test_projector_pjlink_commands_02
mock_socket_timer = patch.object(self.pjlink_test, 'socket_timer').start() # Keeping here for bug reference
mock_timer = patch.object(self.pjlink_test, 'timer').start() pass
mock_send_command = patch.object(self.pjlink_test, 'write').start()
mock_state = patch.object(self.pjlink_test, 'state').start()
mock_waitForReadyRead = patch.object(self.pjlink_test, 'waitForReadyRead').start()
pjlink = self.pjlink_test
pjlink.pin = TEST_PIN
mock_state.return_value = pjlink.ConnectedState
mock_waitForReadyRead.return_value = True
# WHEN: Athenticated connection is called
pjlink.check_login(data=TEST_CONNECT_AUTHENTICATE)
# THEN: send_command should have the proper authentication
self.assertEqual("{test}".format(test=mock_send_command.call_args),
"call(b'{hash}%1CLSS ?\\r')".format(hash=TEST_HASH))
def test_bug_1734275_process_lamp_nonstandard_reply(self): def test_bug_1734275_process_lamp_nonstandard_reply(self):
""" """

View File

@ -25,11 +25,11 @@ Package to test the openlp.core.projectors.pjlink base package.
from unittest import TestCase from unittest import TestCase
from unittest.mock import call, patch, MagicMock from unittest.mock import call, patch, MagicMock
from openlp.core.projectors.constants import E_PARAMETER, ERROR_STRING, S_ON, S_CONNECTED from openlp.core.projectors.constants import E_PARAMETER, ERROR_STRING, S_ON, S_CONNECTED, S_QSOCKET_STATE
from openlp.core.projectors.db import Projector from openlp.core.projectors.db import Projector
from openlp.core.projectors.pjlink import PJLink from openlp.core.projectors.pjlink import PJLink
from tests.resources.projector.data import TEST_PIN, TEST_SALT, TEST_CONNECT_AUTHENTICATE, TEST1_DATA from tests.resources.projector.data import TEST1_DATA
pjlink_test = PJLink(Projector(**TEST1_DATA), no_poll=True) pjlink_test = PJLink(Projector(**TEST1_DATA), no_poll=True)
@ -38,29 +38,17 @@ class TestPJLinkBase(TestCase):
""" """
Tests for the PJLink module Tests for the PJLink module
""" """
@patch.object(pjlink_test, 'readyRead') def setUp(self):
@patch.object(pjlink_test, 'send_command') '''
@patch.object(pjlink_test, 'waitForReadyRead') TestPJLinkCommands part 2 initialization
@patch('openlp.core.common.qmd5_hash') '''
def test_authenticated_connection_call(self, self.pjlink_test = PJLink(Projector(**TEST1_DATA), no_poll=True)
mock_qmd5_hash,
mock_waitForReadyRead,
mock_send_command,
mock_readyRead):
"""
Ticket 92187: Fix for projector connect with PJLink authentication exception.
"""
# GIVEN: Test object
pjlink = pjlink_test
# WHEN: Calling check_login with authentication request: def tearDown(self):
pjlink.check_login(data=TEST_CONNECT_AUTHENTICATE) '''
TestPJLinkCommands part 2 cleanups
# THEN: Should have called qmd5_hash '''
self.assertTrue(mock_qmd5_hash.called_with(TEST_SALT, self.pjlink_test = None
"Connection request should have been called with TEST_SALT"))
self.assertTrue(mock_qmd5_hash.called_with(TEST_PIN,
"Connection request should have been called with TEST_PIN"))
@patch.object(pjlink_test, 'change_status') @patch.object(pjlink_test, 'change_status')
def test_status_change(self, mock_change_status): def test_status_change(self, mock_change_status):
@ -110,18 +98,18 @@ class TestPJLinkBase(TestCase):
# THEN: poll_loop should exit without calling any other method # THEN: poll_loop should exit without calling any other method
self.assertFalse(pjlink.timer.called, 'Should have returned without calling any other method') self.assertFalse(pjlink.timer.called, 'Should have returned without calling any other method')
@patch.object(pjlink_test, 'send_command') def test_poll_loop_start(self):
def test_poll_loop_start(self, mock_send_command):
""" """
Test PJLink.poll_loop makes correct calls Test PJLink.poll_loop makes correct calls
""" """
# GIVEN: test object and test data # GIVEN: Mocks and test data
pjlink = pjlink_test mock_state = patch.object(self.pjlink_test, 'state').start()
pjlink.state = MagicMock() mock_state.return_value = S_QSOCKET_STATE['ConnectedState']
pjlink.timer = MagicMock() mock_timer = patch.object(self.pjlink_test, 'timer').start()
pjlink.timer.interval = MagicMock() mock_timer.interval.return_value = 10
pjlink.timer.setInterval = MagicMock() mock_send_command = patch.object(self.pjlink_test, 'send_command').start()
pjlink.timer.start = MagicMock()
pjlink = self.pjlink_test
pjlink.poll_time = 20 pjlink.poll_time = 20
pjlink.power = S_ON pjlink.power = S_ON
pjlink.source_available = None pjlink.source_available = None
@ -130,19 +118,17 @@ class TestPJLinkBase(TestCase):
pjlink.model = None pjlink.model = None
pjlink.pjlink_name = None pjlink.pjlink_name = None
pjlink.ConnectedState = S_CONNECTED pjlink.ConnectedState = S_CONNECTED
pjlink.timer.interval.return_value = 10
pjlink.state.return_value = S_CONNECTED
call_list = [ call_list = [
call('POWR', queue=True), call('POWR'),
call('ERST', queue=True), call('ERST'),
call('LAMP', queue=True), call('LAMP'),
call('AVMT', queue=True), call('AVMT'),
call('INPT', queue=True), call('INPT'),
call('INST', queue=True), call('INST'),
call('INFO', queue=True), call('INFO'),
call('INF1', queue=True), call('INF1'),
call('INF2', queue=True), call('INF2'),
call('NAME', queue=True), call('NAME'),
] ]
# WHEN: PJLink.poll_loop is called # WHEN: PJLink.poll_loop is called
@ -150,8 +136,8 @@ class TestPJLinkBase(TestCase):
# THEN: proper calls were made to retrieve projector data # THEN: proper calls were made to retrieve projector data
# First, call to update the timer with the next interval # First, call to update the timer with the next interval
self.assertTrue(pjlink.timer.setInterval.called, 'Should have updated the timer') self.assertTrue(mock_timer.setInterval.called)
# Next, should have called the timer to start # Next, should have called the timer to start
self.assertTrue(pjlink.timer.start.called, 'Should have started the timer') self.assertTrue(mock_timer.start.called, 'Should have started the timer')
# Finally, should have called send_command with a list of projetctor status checks # Finally, should have called send_command with a list of projetctor status checks
mock_send_command.assert_has_calls(call_list, 'Should have queued projector queries') mock_send_command.assert_has_calls(call_list, 'Should have queued projector queries')

View File

@ -46,6 +46,18 @@ class TestPJLinkRouting(TestCase):
""" """
Tests for the PJLink module command routing Tests for the PJLink module command routing
""" """
def setUp(self):
'''
TestPJLinkCommands part 2 initialization
'''
self.pjlink_test = PJLink(Projector(**TEST1_DATA), no_poll=True)
def tearDown(self):
'''
TestPJLinkCommands part 2 cleanups
'''
self.pjlink_test = None
@patch.object(openlp.core.projectors.pjlink, 'log') @patch.object(openlp.core.projectors.pjlink, 'log')
def test_process_command_call_clss(self, mock_log): def test_process_command_call_clss(self, mock_log):
""" """
@ -163,21 +175,20 @@ class TestPJLinkRouting(TestCase):
mock_change_status.assert_called_once_with(E_AUTHENTICATION) mock_change_status.assert_called_once_with(E_AUTHENTICATION)
mock_log.error.assert_called_with(log_text) mock_log.error.assert_called_with(log_text)
@patch.object(openlp.core.projectors.pjlink, 'log') def test_process_command_future(self):
def test_process_command_future(self, mock_log):
""" """
Test command valid but no method to process yet Test command valid but no method to process yet
""" """
# GIVEN: Test object # GIVEN: Initial mocks and data
pjlink = pjlink_test mock_log = patch.object(openlp.core.projectors.pjlink, 'log').start()
log_text = "(127.0.0.1) Unable to process command='CLSS' (Future option)" mock_functions = patch.object(self.pjlink_test, 'pjlink_functions').start()
mock_log.reset_mock() mock_functions.return_value = []
# Remove a valid command so we can test valid command but not available yet
pjlink.pjlink_functions.pop('CLSS') pjlink = self.pjlink_test
log_text = '(111.111.111.111) Unable to process command="CLSS" (Future option?)'
# WHEN: process_command called with an unknown command # WHEN: process_command called with an unknown command
with patch.object(pjlink, 'pjlink_functions') as mock_functions: pjlink.process_command(cmd='CLSS', data='DONT CARE')
pjlink.process_command(cmd='CLSS', data='DONT CARE')
# THEN: Error should be logged and no command called # THEN: Error should be logged and no command called
self.assertFalse(mock_functions.called, 'Should not have gotten to the end of the method') self.assertFalse(mock_functions.called, 'Should not have gotten to the end of the method')
@ -196,29 +207,26 @@ class TestPJLinkRouting(TestCase):
# WHEN: process_command called with an unknown command # WHEN: process_command called with an unknown command
pjlink.process_command(cmd='Unknown', data='Dont Care') pjlink.process_command(cmd='Unknown', data='Dont Care')
log_text = "(127.0.0.1) Ignoring command='Unknown' (Invalid/Unknown)" log_text = '(127.0.0.1) Ignoring command="Unknown" (Invalid/Unknown)'
# THEN: Error should be logged and no command called # THEN: Error should be logged and no command called
self.assertFalse(mock_functions.called, 'Should not have gotten to the end of the method') self.assertFalse(mock_functions.called, 'Should not have gotten to the end of the method')
mock_log.error.assert_called_once_with(log_text) mock_log.error.assert_called_once_with(log_text)
@patch.object(pjlink_test, 'pjlink_functions') def test_process_command_ok(self):
@patch.object(openlp.core.projectors.pjlink, 'log')
def test_process_command_ok(self, mock_log, mock_functions):
""" """
Test command returned success Test command returned success
""" """
# GIVEN: Test object # GIVEN: Initial mocks and data
pjlink = pjlink_test mock_log = patch.object(openlp.core.projectors.pjlink, 'log').start()
mock_functions.reset_mock() mock_send_command = patch.object(self.pjlink_test, 'send_command').start()
mock_log.reset_mock()
# WHEN: process_command called with an unknown command pjlink = self.pjlink_test
pjlink.process_command(cmd='CLSS', data='OK') log_text = '(111.111.111.111) Command "POWR" returned OK'
log_text = '(127.0.0.1) Command "CLSS" returned OK'
# THEN: Error should be logged and no command called # WHEN: process_command called with a command that returns OK
self.assertFalse(mock_functions.called, 'Should not have gotten to the end of the method') pjlink.process_command(cmd='POWR', data='OK')
self.assertEqual(mock_log.debug.call_count, 2, 'log.debug() should have been called twice')
# Although we called it twice, only the last log entry is saved # THEN: Appropriate calls should have been made
mock_log.debug.assert_called_with(log_text) mock_log.debug.assert_called_with(log_text)
mock_send_command.assert_called_once_with(cmd='POWR')

View File

@ -47,7 +47,7 @@ for pos in range(0, len(PJLINK_ERST_DATA)):
class TestPJLinkCommands(TestCase): class TestPJLinkCommands(TestCase):
""" """
Tests for the PJLink module Tests for the PJLinkCommands class part 1
""" """
@patch.object(pjlink_test, 'changeStatus') @patch.object(pjlink_test, 'changeStatus')
@patch.object(openlp.core.projectors.pjlink, 'log') @patch.object(openlp.core.projectors.pjlink, 'log')
@ -580,7 +580,7 @@ class TestPJLinkCommands(TestCase):
# WHEN: Process invalid reply # WHEN: Process invalid reply
pjlink.process_clss('Z') pjlink.process_clss('Z')
log_text = "(127.0.0.1) NAN clss version reply 'Z' - defaulting to class '1'" log_text = '(127.0.0.1) NAN CLSS version reply "Z" - defaulting to class "1"'
# THEN: Projector class should be set with default value # THEN: Projector class should be set with default value
self.assertEqual(pjlink.pjlink_class, '1', self.assertEqual(pjlink.pjlink_class, '1',
@ -597,7 +597,7 @@ class TestPJLinkCommands(TestCase):
# WHEN: Process invalid reply # WHEN: Process invalid reply
pjlink.process_clss('Invalid') pjlink.process_clss('Invalid')
log_text = "(127.0.0.1) No numbers found in class version reply 'Invalid' - defaulting to class '1'" log_text = '(127.0.0.1) No numbers found in class version reply "Invalid" - defaulting to class "1"'
# THEN: Projector class should be set with default value # THEN: Projector class should be set with default value
self.assertEqual(pjlink.pjlink_class, '1', self.assertEqual(pjlink.pjlink_class, '1',
@ -627,7 +627,7 @@ class TestPJLinkCommands(TestCase):
# GIVEN: Test object # GIVEN: Test object
pjlink = pjlink_test pjlink = pjlink_test
pjlink.projector_errors = None pjlink.projector_errors = None
log_text = "127.0.0.1) Invalid error status response '11111111': length != 6" log_text = '127.0.0.1) Invalid error status response "11111111": length != 6'
# WHEN: process_erst called with invalid data (too many values # WHEN: process_erst called with invalid data (too many values
pjlink.process_erst('11111111') pjlink.process_erst('11111111')
@ -645,7 +645,7 @@ class TestPJLinkCommands(TestCase):
# GIVEN: Test object # GIVEN: Test object
pjlink = pjlink_test pjlink = pjlink_test
pjlink.projector_errors = None pjlink.projector_errors = None
log_text = "(127.0.0.1) Invalid error status response '1111Z1'" log_text = '(127.0.0.1) Invalid error status response "1111Z1"'
# WHEN: process_erst called with invalid data (too many values # WHEN: process_erst called with invalid data (too many values
pjlink.process_erst('1111Z1') pjlink.process_erst('1111Z1')
@ -671,8 +671,8 @@ class TestPJLinkCommands(TestCase):
# THEN: PJLink instance errors should match chk_value # THEN: PJLink instance errors should match chk_value
for chk in pjlink.projector_errors: for chk in pjlink.projector_errors:
self.assertEqual(pjlink.projector_errors[chk], chk_string, self.assertEqual(pjlink.projector_errors[chk], chk_string,
"projector_errors['{chk}'] should have been set to {err}".format(chk=chk, 'projector_errors["{chk}"] should have been set to "{err}"'.format(chk=chk,
err=chk_string)) err=chk_string))
def test_projector_process_erst_all_error(self): def test_projector_process_erst_all_error(self):
""" """
@ -690,8 +690,8 @@ class TestPJLinkCommands(TestCase):
# THEN: PJLink instance errors should match chk_value # THEN: PJLink instance errors should match chk_value
for chk in pjlink.projector_errors: for chk in pjlink.projector_errors:
self.assertEqual(pjlink.projector_errors[chk], chk_string, self.assertEqual(pjlink.projector_errors[chk], chk_string,
"projector_errors['{chk}'] should have been set to {err}".format(chk=chk, 'projector_errors["{chk}"] should have been set to "{err}"'.format(chk=chk,
err=chk_string)) err=chk_string))
def test_projector_process_erst_warn_cover_only(self): def test_projector_process_erst_warn_cover_only(self):
""" """
@ -744,9 +744,9 @@ class TestPJLinkCommands(TestCase):
pjlink = pjlink_test pjlink = pjlink_test
pjlink.source_available = [] pjlink.source_available = []
test_data = '21 10 30 31 11 20' test_data = '21 10 30 31 11 20'
test_saved = ['10', '11', '20', '21', '30', '31'] test_saved = ["10", "11", "20", "21", "30", "31"]
log_data = '(127.0.0.1) Setting projector sources_available to ' \ log_data = "(127.0.0.1) Setting projector sources_available to " \
'"[\'10\', \'11\', \'20\', \'21\', \'30\', \'31\']"' "\"['10', '11', '20', '21', '30', '31']\""
mock_UpdateIcons.reset_mock() mock_UpdateIcons.reset_mock()
mock_log.reset_mock() mock_log.reset_mock()
@ -1021,7 +1021,7 @@ class TestPJLinkCommands(TestCase):
pjlink.sw_version = None pjlink.sw_version = None
pjlink.sw_version_received = None pjlink.sw_version_received = None
test_data = 'Test 1 Subtest 1' test_data = 'Test 1 Subtest 1'
test_log = "(127.0.0.1) Setting projector software version to 'Test 1 Subtest 1'" test_log = '(127.0.0.1) Setting projector software version to "Test 1 Subtest 1"'
mock_log.reset_mock() mock_log.reset_mock()
# WHEN: process_sver called with invalid data # WHEN: process_sver called with invalid data

View File

@ -0,0 +1,198 @@
# -*- 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 #
###############################################################################
"""
Package to test the openlp.core.projectors.pjlink commands package.
"""
from unittest import TestCase
from unittest.mock import patch, call
import openlp.core.projectors.pjlink
from openlp.core.projectors.constants import S_CONNECTED
from openlp.core.projectors.db import Projector
from openlp.core.projectors.pjlink import PJLink
from tests.resources.projector.data import TEST_HASH, TEST_PIN, TEST_SALT, TEST1_DATA
class TestPJLinkCommands(TestCase):
"""
Tests for the PJLinkCommands class part 2
"""
def setUp(self):
'''
TestPJLinkCommands part 2 initialization
'''
self.pjlink_test = PJLink(Projector(**TEST1_DATA), no_poll=True)
def tearDown(self):
'''
TestPJLinkCommands part 2 cleanups
'''
self.pjlink_test = None
def test_process_pjlink_normal(self):
"""
Test initial connection prompt with no authentication
"""
# GIVEN: Initial mocks and data
mock_log = patch.object(openlp.core.projectors.pjlink, "log").start()
mock_disconnect_from_host = patch.object(self.pjlink_test, 'disconnect_from_host').start()
mock_send_command = patch.object(self.pjlink_test, 'send_command').start()
mock_readyRead = patch.object(self.pjlink_test, 'readyRead').start()
mock_change_status = patch.object(self.pjlink_test, 'change_status').start()
pjlink = self.pjlink_test
pjlink.pin = None
log_check = [call("({111.111.111.111}) process_pjlink(): Sending 'CLSS' initial command'"), ]
# WHEN: process_pjlink called with no authentication required
pjlink.process_pjlink(data="0")
# THEN: proper processing should have occured
mock_log.debug.has_calls(log_check)
mock_disconnect_from_host.assert_not_called()
self.assertEqual(mock_readyRead.connect.call_count, 1, 'Should have only been called once')
mock_change_status.assert_called_once_with(S_CONNECTED)
mock_send_command.assert_called_with(cmd='CLSS', priority=True, salt=None)
def test_process_pjlink_authenticate(self):
"""
Test initial connection prompt with authentication
"""
# GIVEN: Initial mocks and data
mock_log = patch.object(openlp.core.projectors.pjlink, "log").start()
mock_disconnect_from_host = patch.object(self.pjlink_test, 'disconnect_from_host').start()
mock_send_command = patch.object(self.pjlink_test, 'send_command').start()
mock_readyRead = patch.object(self.pjlink_test, 'readyRead').start()
mock_change_status = patch.object(self.pjlink_test, 'change_status').start()
pjlink = self.pjlink_test
pjlink.pin = TEST_PIN
log_check = [call("({111.111.111.111}) process_pjlink(): Sending 'CLSS' initial command'"), ]
# WHEN: process_pjlink called with no authentication required
pjlink.process_pjlink(data='1 {salt}'.format(salt=TEST_SALT))
# THEN: proper processing should have occured
mock_log.debug.has_calls(log_check)
mock_disconnect_from_host.assert_not_called()
self.assertEqual(mock_readyRead.connect.call_count, 1, 'Should have only been called once')
mock_change_status.assert_called_once_with(S_CONNECTED)
mock_send_command.assert_called_with(cmd='CLSS', priority=True, salt=TEST_HASH)
def test_process_pjlink_normal_pin_set_error(self):
"""
Test process_pjlinnk called with no authentication but pin is set
"""
# GIVEN: Initial mocks and data
# GIVEN: Initial mocks and data
mock_log = patch.object(openlp.core.projectors.pjlink, 'log').start()
mock_disconnect_from_host = patch.object(self.pjlink_test, 'disconnect_from_host').start()
mock_send_command = patch.object(self.pjlink_test, 'send_command').start()
pjlink = self.pjlink_test
pjlink.pin = TEST_PIN
log_check = [call('(111.111.111.111) Normal connection but PIN set - aborting'), ]
# WHEN: process_pjlink called with invalid authentication scheme
pjlink.process_pjlink(data='0')
# THEN: Proper calls should be made
mock_log.error.assert_has_calls(log_check)
self.assertEqual(mock_disconnect_from_host.call_count, 1, 'Should have only been called once')
mock_send_command.assert_not_called()
def test_process_pjlink_normal_with_salt_error(self):
"""
Test process_pjlinnk called with no authentication but pin is set
"""
# GIVEN: Initial mocks and data
# GIVEN: Initial mocks and data
mock_log = patch.object(openlp.core.projectors.pjlink, 'log').start()
mock_disconnect_from_host = patch.object(self.pjlink_test, 'disconnect_from_host').start()
mock_send_command = patch.object(self.pjlink_test, 'send_command').start()
pjlink = self.pjlink_test
pjlink.pin = TEST_PIN
log_check = [call('(111.111.111.111) Normal connection with extra information - aborting'), ]
# WHEN: process_pjlink called with invalid authentication scheme
pjlink.process_pjlink(data='0 {salt}'.format(salt=TEST_SALT))
# THEN: Proper calls should be made
mock_log.error.assert_has_calls(log_check)
self.assertEqual(mock_disconnect_from_host.call_count, 1, 'Should have only been called once')
mock_send_command.assert_not_called()
def test_process_pjlink_invalid_authentication_scheme_length_error(self):
"""
Test initial connection prompt with authentication scheme longer than 1 character
"""
# GIVEN: Initial mocks and data
mock_log = patch.object(openlp.core.projectors.pjlink, 'log').start()
mock_disconnect_from_host = patch.object(self.pjlink_test, 'disconnect_from_host').start()
mock_send_command = patch.object(self.pjlink_test, 'send_command').start()
pjlink = self.pjlink_test
log_check = [call('(111.111.111.111) Invalid initial authentication scheme - aborting'), ]
# WHEN: process_pjlink called with invalid authentication scheme
pjlink.process_pjlink(data='01')
# THEN: socket should be closed and invalid data logged
mock_log.error.assert_has_calls(log_check)
self.assertEqual(mock_disconnect_from_host.call_count, 1, 'Should have only been called once')
mock_send_command.assert_not_called()
def test_process_pjlink_invalid_authentication_data_length_error(self):
"""
Test initial connection prompt with authentication no salt
"""
# GIVEN: Initial mocks and data
mock_log = patch.object(openlp.core.projectors.pjlink, 'log').start()
mock_disconnect_from_host = patch.object(self.pjlink_test, 'disconnect_from_host').start()
mock_send_command = patch.object(self.pjlink_test, 'send_command').start()
log_check = [call('(111.111.111.111) Authenticated connection but not enough info - aborting'), ]
pjlink = self.pjlink_test
# WHEN: process_pjlink called with no salt
pjlink.process_pjlink(data='1')
# THEN: socket should be closed and invalid data logged
mock_log.error.assert_has_calls(log_check)
self.assertEqual(mock_disconnect_from_host.call_count, 1, 'Should have only been called once')
mock_send_command.assert_not_called()
def test_process_pjlink_authenticate_pin_not_set_error(self):
"""
Test process_pjlink authentication but pin not set
"""
# GIVEN: Initial mocks and data
mock_log = patch.object(openlp.core.projectors.pjlink, 'log').start()
mock_disconnect_from_host = patch.object(self.pjlink_test, 'disconnect_from_host').start()
mock_send_command = patch.object(self.pjlink_test, 'send_command').start()
log_check = [call('(111.111.111.111) Authenticate connection but no PIN - aborting'), ]
pjlink = self.pjlink_test
pjlink.pin = None
# WHEN: process_pjlink called with no salt
pjlink.process_pjlink(data='1 {salt}'.format(salt=TEST_SALT))
# THEN: socket should be closed and invalid data logged
mock_log.error.assert_has_calls(log_check)
self.assertEqual(mock_disconnect_from_host.call_count, 1, 'Should have only been called once')
mock_send_command.assert_not_called()

View File

@ -693,9 +693,9 @@ class TestVLCPlayer(TestCase, TestMixin):
vlc_player.set_state(MediaState.Paused, mocked_display) vlc_player.set_state(MediaState.Paused, mocked_display)
# WHEN: play() is called # WHEN: play() is called
with patch.object(vlc_player, 'media_state_wait') as mocked_media_state_wait, \ with patch.object(vlc_player, 'media_state_wait', return_value=True) as mocked_media_state_wait, \
patch.object(vlc_player, 'volume') as mocked_volume: patch.object(vlc_player, 'volume') as mocked_volume, \
mocked_media_state_wait.return_value = True patch.object(vlc_player, 'get_live_state', return_value=MediaState.Loaded):
result = vlc_player.play(mocked_display) result = vlc_player.play(mocked_display)
# THEN: A bunch of things should happen to play the media # THEN: A bunch of things should happen to play the media
@ -872,7 +872,7 @@ class TestVLCPlayer(TestCase, TestMixin):
mocked_display = MagicMock() mocked_display = MagicMock()
mocked_display.controller.media_info.media_type = MediaType.DVD mocked_display.controller.media_info.media_type = MediaType.DVD
mocked_display.vlc_media_player.is_seekable.return_value = True mocked_display.vlc_media_player.is_seekable.return_value = True
mocked_display.controller.media_info.start_time = 3 mocked_display.controller.media_info.start_time = 3000
vlc_player = VlcPlayer(None) vlc_player = VlcPlayer(None)
# WHEN: seek() is called # WHEN: seek() is called
@ -976,7 +976,7 @@ class TestVLCPlayer(TestCase, TestMixin):
mocked_display = MagicMock() mocked_display = MagicMock()
mocked_display.controller = mocked_controller mocked_display.controller = mocked_controller
mocked_display.vlc_media.get_state.return_value = 1 mocked_display.vlc_media.get_state.return_value = 1
mocked_display.vlc_media_player.get_time.return_value = 400000 mocked_display.vlc_media_player.get_time.return_value = 400
mocked_display.controller.media_info.media_type = MediaType.DVD mocked_display.controller.media_info.media_type = MediaType.DVD
vlc_player = VlcPlayer(None) vlc_player = VlcPlayer(None)
@ -990,7 +990,7 @@ class TestVLCPlayer(TestCase, TestMixin):
self.assertEqual(2, mocked_stop.call_count) self.assertEqual(2, mocked_stop.call_count)
mocked_display.vlc_media_player.get_time.assert_called_with() mocked_display.vlc_media_player.get_time.assert_called_with()
mocked_set_visible.assert_called_with(mocked_display, False) mocked_set_visible.assert_called_with(mocked_display, False)
mocked_controller.seek_slider.setSliderPosition.assert_called_with(300000) mocked_controller.seek_slider.setSliderPosition.assert_called_with(300)
expected_calls = [call(True), call(False)] expected_calls = [call(True), call(False)]
self.assertEqual(expected_calls, mocked_controller.seek_slider.blockSignals.call_args_list) self.assertEqual(expected_calls, mocked_controller.seek_slider.blockSignals.call_args_list)

View File

@ -22,18 +22,20 @@
""" """
This module contains tests for the OpenOffice/LibreOffice importer. This module contains tests for the OpenOffice/LibreOffice importer.
""" """
from unittest import TestCase, SkipTest from unittest import TestCase, skipIf
from unittest.mock import MagicMock, patch from unittest.mock import MagicMock, patch
from openlp.core.common.registry import Registry from openlp.core.common.registry import Registry
try:
from openlp.plugins.songs.lib.importers.openoffice import OpenOfficeImport
except ImportError:
raise SkipTest('Could not import OpenOfficeImport probably due to unavailability of uno')
from tests.helpers.testmixin import TestMixin from tests.helpers.testmixin import TestMixin
try:
from openlp.plugins.songs.lib.importers.openoffice import OpenOfficeImport
except ImportError:
OpenOfficeImport = None
@skipIf(OpenOfficeImport is None, 'Could not import OpenOfficeImport probably due to unavailability of uno')
class TestOpenOfficeImport(TestCase, TestMixin): class TestOpenOfficeImport(TestCase, TestMixin):
""" """
Test the :class:`~openlp.plugins.songs.lib.importer.openoffice.OpenOfficeImport` class Test the :class:`~openlp.plugins.songs.lib.importer.openoffice.OpenOfficeImport` class