This commit is contained in:
Raoul Snyman 2017-08-02 21:21:19 -07:00
commit 2e43e7939e
45 changed files with 807 additions and 268 deletions

View File

@ -181,7 +181,7 @@ class OpenLP(OpenLPMixin, QtWidgets.QApplication):
"""
Check if the data folder path exists.
"""
data_folder_path = AppLocation.get_data_path()
data_folder_path = str(AppLocation.get_data_path())
if not os.path.exists(data_folder_path):
log.critical('Database was not found in: ' + data_folder_path)
status = QtWidgets.QMessageBox.critical(None, translate('OpenLP', 'Data Directory Error'),
@ -253,7 +253,7 @@ class OpenLP(OpenLPMixin, QtWidgets.QApplication):
'a backup of the old data folder?'),
defaultButton=QtWidgets.QMessageBox.Yes) == QtWidgets.QMessageBox.Yes:
# Create copy of data folder
data_folder_path = AppLocation.get_data_path()
data_folder_path = str(AppLocation.get_data_path())
timestamp = time.strftime("%Y%m%d-%H%M%S")
data_folder_backup_path = data_folder_path + '-' + timestamp
try:
@ -390,7 +390,7 @@ def main(args=None):
application.setApplicationName('OpenLPPortable')
Settings.setDefaultFormat(Settings.IniFormat)
# Get location OpenLPPortable.ini
application_path = AppLocation.get_directory(AppLocation.AppDir)
application_path = str(AppLocation.get_directory(AppLocation.AppDir))
set_up_logging(os.path.abspath(os.path.join(application_path, '..', '..', 'Other')))
log.info('Running portable')
portable_settings_file = os.path.abspath(os.path.join(application_path, '..', '..', 'Data', 'OpenLP.ini'))
@ -407,7 +407,7 @@ def main(args=None):
portable_settings.sync()
else:
application.setApplicationName('OpenLP')
set_up_logging(AppLocation.get_directory(AppLocation.CacheDir))
set_up_logging(str(AppLocation.get_directory(AppLocation.CacheDir)))
Registry.create()
Registry().register('application', application)
application.setApplicationVersion(get_application_version()['version'])

View File

@ -95,9 +95,9 @@ def extension_loader(glob_pattern, excluded_files=[]):
:return: None
:rtype: None
"""
app_dir = Path(AppLocation.get_directory(AppLocation.AppDir)).parent
for extension_path in app_dir.glob(glob_pattern):
extension_path = extension_path.relative_to(app_dir)
base_dir_path = AppLocation.get_directory(AppLocation.AppDir).parent
for extension_path in base_dir_path.glob(glob_pattern):
extension_path = extension_path.relative_to(base_dir_path)
if extension_path.name in excluded_files:
continue
module_name = path_to_module(extension_path)

View File

@ -25,6 +25,7 @@ The :mod:`openlp.core.common.applocation` module provides an utility for OpenLP
import logging
import os
import sys
from pathlib import Path
from openlp.core.common import Settings, is_win, is_macosx
@ -42,6 +43,9 @@ from openlp.core.common import check_directory_exists, get_frozen_path
log = logging.getLogger(__name__)
FROZEN_APP_PATH = Path(sys.argv[0]).parent
APP_PATH = Path(openlp.__file__).parent
class AppLocation(object):
"""
@ -63,20 +67,19 @@ class AppLocation(object):
Return the appropriate directory according to the directory type.
:param dir_type: The directory type you want, for instance the data directory. Default *AppLocation.AppDir*
:type dir_type: AppLocation Enum
:return: The requested path
:rtype: pathlib.Path
"""
if dir_type == AppLocation.AppDir:
return get_frozen_path(os.path.abspath(os.path.dirname(sys.argv[0])), os.path.dirname(openlp.__file__))
if dir_type == AppLocation.AppDir or dir_type == AppLocation.VersionDir:
return get_frozen_path(FROZEN_APP_PATH, APP_PATH)
elif dir_type == AppLocation.PluginsDir:
app_path = os.path.abspath(os.path.dirname(sys.argv[0]))
return get_frozen_path(os.path.join(app_path, 'plugins'),
os.path.join(os.path.dirname(openlp.__file__), 'plugins'))
elif dir_type == AppLocation.VersionDir:
return get_frozen_path(os.path.abspath(os.path.dirname(sys.argv[0])), os.path.dirname(openlp.__file__))
return get_frozen_path(FROZEN_APP_PATH, APP_PATH) / 'plugins'
elif dir_type == AppLocation.LanguageDir:
app_path = get_frozen_path(os.path.abspath(os.path.dirname(sys.argv[0])), _get_os_dir_path(dir_type))
return os.path.join(app_path, 'i18n')
return get_frozen_path(FROZEN_APP_PATH, _get_os_dir_path(dir_type)) / 'i18n'
elif dir_type == AppLocation.DataDir and AppLocation.BaseDir:
return os.path.join(AppLocation.BaseDir, 'data')
return Path(AppLocation.BaseDir, 'data')
else:
return _get_os_dir_path(dir_type)
@ -84,84 +87,97 @@ class AppLocation(object):
def get_data_path():
"""
Return the path OpenLP stores all its data under.
:return: The data path to use.
:rtype: pathlib.Path
"""
# Check if we have a different data location.
if Settings().contains('advanced/data path'):
path = Settings().value('advanced/data path')
path = Path(Settings().value('advanced/data path'))
else:
path = AppLocation.get_directory(AppLocation.DataDir)
check_directory_exists(path)
return os.path.normpath(path)
check_directory_exists(str(path))
return path
@staticmethod
def get_files(section=None, extension=None):
def get_files(section=None, extension=''):
"""
Get a list of files from the data files path.
:param section: Defaults to *None*. The section of code getting the files - used to load from a section's
data subdirectory.
:param extension:
Defaults to *None*. The extension to search for. For example::
:param section: Defaults to *None*. The section of code getting the files - used to load from a section's data
subdirectory.
:type section: None | str
'.png'
:param extension: Defaults to ''. The extension to search for. For example::
'.png'
:type extension: str
:return: List of files found.
:rtype: list[pathlib.Path]
"""
path = AppLocation.get_data_path()
if section:
path = os.path.join(path, section)
path = path / section
try:
files = os.listdir(path)
file_paths = path.glob('*' + extension)
return [file_path.relative_to(path) for file_path in file_paths]
except OSError:
return []
if extension:
return [filename for filename in files if extension == os.path.splitext(filename)[1]]
else:
# no filtering required
return files
@staticmethod
def get_section_data_path(section):
"""
Return the path a particular module stores its data under.
:type section: str
:rtype: pathlib.Path
"""
data_path = AppLocation.get_data_path()
path = os.path.join(data_path, section)
check_directory_exists(path)
path = AppLocation.get_data_path() / section
check_directory_exists(str(path))
return path
def _get_os_dir_path(dir_type):
"""
Return a path based on which OS and environment we are running in.
:param dir_type: AppLocation Enum of the requested path type
:type dir_type: AppLocation Enum
:return: The requested path
:rtype: pathlib.Path
"""
# If running from source, return the language directory from the source directory
if dir_type == AppLocation.LanguageDir:
directory = os.path.abspath(os.path.join(os.path.dirname(openlp.__file__), '..', 'resources'))
if os.path.exists(directory):
directory = Path(os.path.abspath(os.path.join(os.path.dirname(openlp.__file__), '..', 'resources')))
if directory.exists():
return directory
if is_win():
openlp_folder_path = Path(os.getenv('APPDATA'), 'openlp')
if dir_type == AppLocation.DataDir:
return os.path.join(str(os.getenv('APPDATA')), 'openlp', 'data')
return openlp_folder_path / 'data'
elif dir_type == AppLocation.LanguageDir:
return os.path.dirname(openlp.__file__)
return os.path.join(str(os.getenv('APPDATA')), 'openlp')
return openlp_folder_path
elif is_macosx():
openlp_folder_path = Path(os.getenv('HOME'), 'Library', 'Application Support', 'openlp')
if dir_type == AppLocation.DataDir:
return os.path.join(str(os.getenv('HOME')), 'Library', 'Application Support', 'openlp', 'Data')
return openlp_folder_path / 'Data'
elif dir_type == AppLocation.LanguageDir:
return os.path.dirname(openlp.__file__)
return os.path.join(str(os.getenv('HOME')), 'Library', 'Application Support', 'openlp')
return openlp_folder_path
else:
if dir_type == AppLocation.LanguageDir:
for prefix in ['/usr/local', '/usr']:
directory = os.path.join(prefix, 'share', 'openlp')
if os.path.exists(directory):
return directory
return os.path.join('/usr', 'share', 'openlp')
directory = Path('/usr', 'local', 'share', 'openlp')
if directory.exists():
return directory
return Path('/usr', 'share', 'openlp')
if XDG_BASE_AVAILABLE:
if dir_type == AppLocation.DataDir:
return os.path.join(str(BaseDirectory.xdg_data_home), 'openlp')
if dir_type == AppLocation.DataDir or dir_type == AppLocation.CacheDir:
return Path(BaseDirectory.xdg_data_home, 'openlp')
elif dir_type == AppLocation.CacheDir:
return os.path.join(str(BaseDirectory.xdg_cache_home), 'openlp')
return Path(BaseDirectory.xdg_cache_home, 'openlp')
if dir_type == AppLocation.DataDir:
return os.path.join(str(os.getenv('HOME')), '.openlp', 'data')
return os.path.join(str(os.getenv('HOME')), '.openlp')
return Path(os.getenv('HOME'), '.openlp', 'data')
return Path(os.getenv('HOME'), '.openlp')

View File

@ -53,7 +53,7 @@ class LanguageManager(object):
"""
if LanguageManager.auto_language:
language = QtCore.QLocale.system().name()
lang_path = AppLocation.get_directory(AppLocation.LanguageDir)
lang_path = str(AppLocation.get_directory(AppLocation.LanguageDir))
app_translator = QtCore.QTranslator()
app_translator.load(language, lang_path)
# A translator for buttons and other default strings provided by Qt.
@ -72,7 +72,7 @@ class LanguageManager(object):
Find all available language files in this OpenLP install
"""
log.debug('Translation files: {files}'.format(files=AppLocation.get_directory(AppLocation.LanguageDir)))
trans_dir = QtCore.QDir(AppLocation.get_directory(AppLocation.LanguageDir))
trans_dir = QtCore.QDir(str(AppLocation.get_directory(AppLocation.LanguageDir)))
file_names = trans_dir.entryList(['*.qm'], QtCore.QDir.Files, QtCore.QDir.Name)
# Remove qm files from the list which start with "qt".
file_names = [file_ for file_ in file_names if not file_.startswith('qt')]

View File

@ -95,7 +95,7 @@ def get_application_version():
full_version = '{tag}-bzr{tree}'.format(tag=tag_version.strip(), tree=tree_revision.strip())
else:
# We're not running the development version, let's use the file.
file_path = AppLocation.get_directory(AppLocation.VersionDir)
file_path = str(AppLocation.get_directory(AppLocation.VersionDir))
file_path = os.path.join(file_path, '.version')
version_file = None
try:

View File

@ -274,9 +274,9 @@ def delete_database(plugin_name, db_file_name=None):
:param db_file_name: The database file name. Defaults to None resulting in the plugin_name being used.
"""
if db_file_name:
db_file_path = os.path.join(AppLocation.get_section_data_path(plugin_name), db_file_name)
db_file_path = os.path.join(str(AppLocation.get_section_data_path(plugin_name)), db_file_name)
else:
db_file_path = os.path.join(AppLocation.get_section_data_path(plugin_name), plugin_name)
db_file_path = os.path.join(str(AppLocation.get_section_data_path(plugin_name)), plugin_name)
return delete_file(db_file_path)

61
openlp/core/lib/path.py Normal file
View File

@ -0,0 +1,61 @@
# -*- 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 #
###############################################################################
from pathlib import Path
def path_to_str(path):
"""
A utility function to convert a Path object or NoneType to a string equivalent.
:param path: The value to convert to a string
:type: pathlib.Path or None
:return: An empty string if :param:`path` is None, else a string representation of the :param:`path`
:rtype: str
"""
if not isinstance(path, Path) and path is not None:
raise TypeError('parameter \'path\' must be of type Path or NoneType')
if path is None:
return ''
else:
return str(path)
def str_to_path(string):
"""
A utility function to convert a str object to a Path or NoneType.
This function is of particular use because initating a Path object with an empty string causes the Path object to
point to the current working directory.
:param string: The string to convert
:type string: str
:return: None if :param:`string` is empty, or a Path object representation of :param:`string`
:rtype: pathlib.Path or None
"""
if not isinstance(string, str):
raise TypeError('parameter \'string\' must be of type str')
if string == '':
return None
return Path(string)

View File

@ -40,7 +40,7 @@ class PluginManager(RegistryMixin, OpenLPMixin, RegistryProperties):
"""
super(PluginManager, self).__init__(parent)
self.log_info('Plugin manager Initialising')
self.base_path = os.path.abspath(AppLocation.get_directory(AppLocation.PluginsDir))
self.base_path = os.path.abspath(str(AppLocation.get_directory(AppLocation.PluginsDir)))
self.log_debug('Base path {path}'.format(path=self.base_path))
self.plugins = []
self.log_info('Plugin manager Initialised')

View File

@ -303,7 +303,7 @@ class ProjectorDB(Manager):
:param ip: Host IP/Name
:returns: Projector() instance
"""
log.debug('get_projector_by_ip(ip="%s")' % ip)
log.debug('get_projector_by_ip(ip="{ip}")'.format(ip=ip))
projector = self.get_object_filtered(Projector, Projector.ip == ip)
if projector is None:
# Not found

View File

@ -44,6 +44,7 @@ log.debug('pjlink1 loaded')
__all__ = ['PJLink']
import re
from codecs import decode
from PyQt5 import QtCore, QtNetwork
@ -53,7 +54,7 @@ from openlp.core.lib.projector.constants import CONNECTION_ERRORS, CR, ERROR_MSG
E_AUTHENTICATION, E_CONNECTION_REFUSED, E_GENERAL, E_INVALID_DATA, E_NETWORK, E_NOT_CONNECTED, \
E_PARAMETER, E_PROJECTOR, E_SOCKET_TIMEOUT, E_UNAVAILABLE, E_UNDEFINED, PJLINK_ERRORS, \
PJLINK_ERST_STATUS, PJLINK_MAX_PACKET, PJLINK_PORT, PJLINK_POWR_STATUS, PJLINK_VALID_CMD, \
PJLINK_DEFAULT_CODES, STATUS_STRING, S_CONNECTED, S_CONNECTING, S_NETWORK_RECEIVED, S_NETWORK_SENDING, \
STATUS_STRING, S_CONNECTED, S_CONNECTING, S_NETWORK_RECEIVED, S_NETWORK_SENDING, \
S_NOT_CONNECTED, S_OFF, S_OK, S_ON, S_STATUS
# Shortcuts
@ -113,8 +114,13 @@ class PJLink(QtNetwork.QTcpSocket):
self.port = port
self.pin = pin
super().__init__()
self.model_lamp = None
self.model_filter = None
self.mac_adx = kwargs.get('mac_adx')
self.serial_no = None
self.serial_no_received = None # Used only if saved serial number is different than received serial number
self.dbid = None
self.db_update = False # Use to check if db needs to be updated prior to exiting
self.location = None
self.notes = None
self.dbid = kwargs.get('dbid')
@ -158,7 +164,11 @@ class PJLink(QtNetwork.QTcpSocket):
'LAMP': self.process_lamp,
'NAME': self.process_name,
'PJLINK': self.check_login,
'POWR': self.process_powr
'POWR': self.process_powr,
'SNUM': self.process_snum,
'SVER': self.process_sver,
'RFIL': self.process_rfil,
'RLMP': self.process_rlmp
}
def reset_information(self):
@ -166,12 +176,16 @@ class PJLink(QtNetwork.QTcpSocket):
Reset projector-specific information to default
"""
log.debug('({ip}) reset_information() connect status is {state}'.format(ip=self.ip, state=self.state()))
self.send_queue = []
self.power = S_OFF
self.pjlink_name = None
self.manufacturer = None
self.model = None
self.serial_no = None
self.serial_no_received = None
self.sw_version = None
self.sw_version_received = None
self.mac_adx = None
self.shutter = None
self.mute = None
self.lamp = None
@ -188,7 +202,6 @@ class PJLink(QtNetwork.QTcpSocket):
if hasattr(self, 'socket_timer'):
log.debug('({ip}): Calling socket_timer.stop()'.format(ip=self.ip))
self.socket_timer.stop()
self.send_queue = []
self.send_busy = False
def thread_started(self):
@ -240,6 +253,7 @@ class PJLink(QtNetwork.QTcpSocket):
Normally called by timer().
"""
if self.state() != self.ConnectedState:
log.warn("({ip}) poll_loop(): Not connected - returning".format(ip=self.ip))
return
log.debug('({ip}) Updating projector status'.format(ip=self.ip))
# Reset timer in case we were called from a set command
@ -248,8 +262,11 @@ class PJLink(QtNetwork.QTcpSocket):
self.timer.setInterval(self.poll_time)
# Restart timer
self.timer.start()
# These commands may change during connetion
for command in ['POWR', 'ERST', 'LAMP', 'AVMT', 'INPT']:
# These commands may change during connection
check_list = ['POWR', 'ERST', 'LAMP', 'AVMT', 'INPT']
if self.pjlink_class == '2':
check_list.extend(['FILT', 'FREZ'])
for command in check_list:
self.send_command(command, queue=True)
# The following commands do not change, so only check them once
if self.power == S_ON and self.source_available is None:
@ -262,6 +279,38 @@ class PJLink(QtNetwork.QTcpSocket):
self.send_command('INF2', queue=True)
if self.pjlink_name is None:
self.send_command('NAME', queue=True)
if self.pjlink_class == '2':
# Class 2 specific checks
if self.serial_no is None:
self.send_command('SNUM', queue=True)
if self.sw_version is None:
self.send_command('SVER', queue=True)
if self.model_filter is None:
self.send_command('RFIL', queue=True)
if self.model_lamp is None:
self.send_command('RLMP', queue=True)
def process_rfil(self, data):
"""
Process replacement filter type
"""
if self.model_filter is None:
self.model_filter = data
else:
log.warn("({ip}) Filter model already set".format(ip=self.ip))
log.warn("({ip}) Saved model: '{old}'".format(ip=self.ip, old=self.model_filter))
log.warn("({ip}) New model: '{new}'".format(ip=self.ip, new=data))
def process_rlmp(self, data):
"""
Process replacement lamp type
"""
if self.model_lamp is None:
self.model_lamp = data
else:
log.warn("({ip}) Lamp model already set".format(ip=self.ip))
log.warn("({ip}) Saved lamp: '{old}'".format(ip=self.ip, old=self.model_lamp))
log.warn("({ip}) New lamp: '{new}'".format(ip=self.ip, new=data))
def _get_status(self, status):
"""
@ -331,7 +380,7 @@ class PJLink(QtNetwork.QTcpSocket):
self.change_status(E_SOCKET_TIMEOUT)
return
read = self.readLine(self.max_size)
dontcare = self.readLine(self.max_size) # Clean out the trailing \r\n
self.readLine(self.max_size) # Clean out the trailing \r\n
if read is None:
log.warning('({ip}) read is None - socket error?'.format(ip=self.ip))
return
@ -341,7 +390,7 @@ class PJLink(QtNetwork.QTcpSocket):
data = decode(read, 'utf-8')
# Possibility of extraneous data on input when reading.
# Clean out extraneous characters in buffer.
dontcare = self.readLine(self.max_size)
self.readLine(self.max_size)
log.debug('({ip}) check_login() read "{data}"'.format(ip=self.ip, data=data.strip()))
# At this point, we should only have the initial login prompt with
# possible authentication
@ -363,24 +412,24 @@ class PJLink(QtNetwork.QTcpSocket):
# Authentication error
self.disconnect_from_host()
self.change_status(E_AUTHENTICATION)
log.debug('({ip}) emitting projectorAuthentication() signal'.format(ip=self.name))
log.debug('({ip}) emitting projectorAuthentication() signal'.format(ip=self.ip))
return
elif data_check[1] == '0' and self.pin is not None:
# Pin set and no authentication needed
log.warning('({ip}) Regular connection but PIN set'.format(ip=self.name))
self.disconnect_from_host()
self.change_status(E_AUTHENTICATION)
log.debug('({ip}) Emitting projectorNoAuthentication() signal'.format(ip=self.name))
log.debug('({ip}) Emitting projectorNoAuthentication() signal'.format(ip=self.ip))
self.projectorNoAuthentication.emit(self.name)
return
elif data_check[1] == '1':
# Authenticated login with salt
if self.pin is None:
log.warning('({ip}) Authenticated connection but no pin set'.format(ip=self.name))
log.warning('({ip}) Authenticated connection but no pin set'.format(ip=self.ip))
self.disconnect_from_host()
self.change_status(E_AUTHENTICATION)
log.debug('({ip}) Emitting projectorAuthentication() signal'.format(ip=self.name))
self.projectorAuthentication.emit(self.name)
log.debug('({ip}) Emitting projectorAuthentication() signal'.format(ip=self.ip))
self.projectorAuthentication.emit(self.ip)
return
else:
log.debug('({ip}) Setting hash with salt="{data}"'.format(ip=self.ip, data=data_check[2]))
@ -400,6 +449,20 @@ class PJLink(QtNetwork.QTcpSocket):
self.timer.setInterval(2000) # Set 2 seconds for initial information
self.timer.start()
def _trash_buffer(self, msg=None):
"""
Clean out extraneous stuff in the buffer.
"""
log.warning("({ip}) {message}".format(ip=self.ip, message='Invalid packet' if msg is None else msg))
self.send_busy = False
trash_count = 0
while self.bytesAvailable() > 0:
trash = self.read(self.max_size)
trash_count += len(trash)
log.debug("({ip}) Finished cleaning buffer - {count} bytes dropped".format(ip=self.ip,
count=trash_count))
return
@QtCore.pyqtSlot()
def get_data(self):
"""
@ -414,43 +477,27 @@ class PJLink(QtNetwork.QTcpSocket):
if read == -1:
# No data available
log.debug('({ip}) get_data(): No data available (-1)'.format(ip=self.ip))
self.send_busy = False
self.projectorReceivedData.emit()
return
return self.receive_data_signal()
self.socket_timer.stop()
self.projectorNetwork.emit(S_NETWORK_RECEIVED)
# NOTE: Class2 has changed to some values being UTF-8
data_in = decode(read, 'utf-8')
data = data_in.strip()
if len(data) < 7:
# Not enough data for a packet
log.debug('({ip}) get_data(): Packet length < 7: "{data}"'.format(ip=self.ip, data=data))
self.receive_data_signal()
return
if (len(data) < 7) or (not data.startswith(PJLINK_PREFIX)):
return self._trash_buffer(msg='get_data(): Invalid packet - length or prefix')
elif '=' not in data:
log.warning('({ip}) get_data(): Invalid packet received'.format(ip=self.ip))
self.receive_data_signal()
return
return self._trash_buffer(msg='get_data(): Invalid packet does not have equal')
log.debug('({ip}) get_data(): Checking new data "{data}"'.format(ip=self.ip, data=data))
# At this point, we should have something to work with
if data.upper().startswith('PJLINK'):
# Reconnected from remote host disconnect ?
self.check_login(data)
self.receive_data_signal()
return
data_split = data.split('=')
header, data = data.split('=')
try:
(prefix, version, cmd, data) = (data_split[0][0], data_split[0][1], data_split[0][2:], data_split[1])
version, cmd = header[1], header[2:]
except ValueError as e:
log.warning('({ip}) get_data(): Invalid packet - expected header + command + data'.format(ip=self.ip))
log.warning('({ip}) get_data(): Received data: "{data}"'.format(ip=self.ip, data=data_in.strip()))
self.change_status(E_INVALID_DATA)
self.receive_data_signal()
return
log.warning('({ip}) get_data(): Received data: "{data}"'.format(ip=self.ip, data=data_in.strip()))
return self._trash_buffer('get_data(): Expected header + command + data')
if cmd not in PJLINK_VALID_CMD:
log.warning('({ip}) get_data(): Invalid packet - unknown command "{data}"'.format(ip=self.ip, data=cmd))
self.receive_data_signal()
return
return self._trash_buffer(msg='get_data(): Unknown command "{data}"'.format(data=cmd))
if int(self.pjlink_class) < int(version):
log.warn('({ip}) get_data(): Projector returned class reply higher '
'than projector stated class'.format(ip=self.ip))
@ -506,7 +553,7 @@ class PJLink(QtNetwork.QTcpSocket):
salt='' if salt is None
else ' with hash'))
cmd_ver = PJLINK_VALID_CMD[cmd]['version']
if self.pjlink_class in cmd_ver:
if self.pjlink_class in PJLINK_VALID_CMD[cmd]['version']:
header = PJLINK_HEADER.format(linkclass=self.pjlink_class)
elif len(cmd_ver) == 1 and (int(cmd_ver[0]) < int(self.pjlink_class)):
# Typically a class 1 only command
@ -575,6 +622,7 @@ class PJLink(QtNetwork.QTcpSocket):
self.waitForBytesWritten(2000) # 2 seconds should be enough
if sent == -1:
# Network error?
log.warning("({ip}) _send_command(): -1 received".format(ip=self.ip))
self.change_status(E_NETWORK,
translate('OpenLP.PJLink', 'Error while sending data to projector'))
@ -617,20 +665,17 @@ class PJLink(QtNetwork.QTcpSocket):
elif data.upper() == 'ERR4':
# Projector/display error
self.change_status(E_PROJECTOR)
self.send_busy = False
self.projectorReceivedData.emit()
self.receive_data_signal()
return
# Command succeeded - no extra information
elif data.upper() == 'OK':
log.debug('({ip}) Command returned OK'.format(ip=self.ip))
# A command returned successfully, recheck data
self.send_busy = False
self.projectorReceivedData.emit()
# A command returned successfully
self.receive_data_signal()
return
# Command checks already passed
log.debug('({ip}) Calling function for {cmd}'.format(ip=self.ip, cmd=cmd))
self.send_busy = False
self.projectorReceivedData.emit()
self.receive_data_signal()
self.pjlink_functions[cmd](data)
def process_lamp(self, data):
@ -729,11 +774,22 @@ class PJLink(QtNetwork.QTcpSocket):
:param data: Class that projector supports.
"""
# bug 1550891: Projector returns non-standard class response:
# : Expected: %1CLSS=1
# : Received: %1CLSS=Class 1
# : Expected: '%1CLSS=1'
# : Received: '%1CLSS=Class 1' (Optoma)
# : Received: '%1CLSS=Version1' (BenQ)
if len(data) > 1:
# Split non-standard information from response
clss = data.split()[-1]
log.warn("({ip}) Non-standard CLSS reply: '{data}'".format(ip=self.ip, data=data))
# Due to stupid projectors not following standards (Optoma, BenQ comes to mind),
# AND the different responses that can be received, the semi-permanent way to
# fix the class reply is to just remove all non-digit characters.
try:
clss = re.findall('\d', data)[0] # Should only be the first match
except IndexError:
log.error("({ip}) No numbers found in class version reply - defaulting to class '1'".format(ip=self.ip))
clss = '1'
elif not data.isdigit():
log.error("({ip}) NAN class version reply - defaulting to class '1'".format(ip=self.ip))
clss = '1'
else:
clss = data
self.pjlink_class = clss
@ -845,6 +901,42 @@ class PJLink(QtNetwork.QTcpSocket):
PJLINK_ERST_STATUS[data[5]]
return
def process_snum(self, data):
"""
Serial number of projector.
:param data: Serial number from projector.
"""
if self.serial_no is None:
log.debug("({ip}) Setting projector serial number to '{data}'".format(ip=self.ip, data=data))
self.serial_no = data
self.db_update = False
else:
# Compare serial numbers and see if we got the same projector
if self.serial_no != data:
log.warn("({ip}) Projector serial number does not match saved serial number".format(ip=self.ip))
log.warn("({ip}) Saved: '{old}'".format(ip=self.ip, old=self.serial_no))
log.warn("({ip}) Received: '{new}'".format(ip=self.ip, new=data))
log.warn("({ip}) NOT saving serial number".format(ip=self.ip))
self.serial_no_received = data
def process_sver(self, data):
"""
Software version of projector
"""
if self.sw_version is None:
log.debug("({ip}) Setting projector software version to '{data}'".format(ip=self.ip, data=data))
self.sw_version = data
self.db_update = True
else:
# Compare software version and see if we got the same projector
if self.serial_no != data:
log.warn("({ip}) Projector software version does not match saved software version".format(ip=self.ip))
log.warn("({ip}) Saved: '{old}'".format(ip=self.ip, old=self.sw_version))
log.warn("({ip}) Received: '{new}'".format(ip=self.ip, new=data))
log.warn("({ip}) NOT saving serial number".format(ip=self.ip))
self.sw_version_received = data
def connect_to_host(self):
"""
Initiate connection to projector.

View File

@ -71,10 +71,10 @@ log = logging.getLogger(__name__)
log.debug('pjlink2 loaded')
from PyQt5 import QtCore, QtNetwork
from PyQt5 import QtNetwork
class PJLinkUDP(QtNetwork.QTcpSocket):
class PJLinkUDP(QtNetwork.QUdpSocket):
"""
Socket service for handling datagram (UDP) sockets.
"""

View File

@ -25,12 +25,9 @@ backend for the projector setup.
"""
import logging
# Not all imports used at this time, but keep for future upgrades
from sqlalchemy import Table, Column, types, inspect
from sqlalchemy.exc import NoSuchTableError
from sqlalchemy import Table, Column, types
from sqlalchemy.sql.expression import null
from openlp.core.common.db import drop_columns
from openlp.core.lib.db import get_upgrade_op
log = logging.getLogger(__name__)

View File

@ -335,7 +335,7 @@ class ServiceItem(RegistryProperties):
if image and not self.has_original_files and self.name == 'presentations':
file_location = os.path.join(path, file_name)
file_location_hash = md5_hash(file_location.encode('utf-8'))
image = os.path.join(AppLocation.get_section_data_path(self.name), 'thumbnails',
image = os.path.join(str(AppLocation.get_section_data_path(self.name)), 'thumbnails',
file_location_hash, ntpath.basename(image))
self._raw_frames.append({'title': file_name, 'image': image, 'path': path,
'display_title': display_title, 'notes': notes})

View File

@ -158,7 +158,7 @@ class Theme(object):
Initialise the theme object.
"""
# basic theme object with defaults
json_dir = os.path.join(AppLocation.get_directory(AppLocation.AppDir), 'core', 'lib', 'json')
json_dir = os.path.join(str(AppLocation.get_directory(AppLocation.AppDir)), 'core', 'lib', 'json')
json_file = os.path.join(json_dir, 'theme.json')
jsn = get_text_file_string(json_file)
jsn = json.loads(jsn)

View File

@ -156,7 +156,7 @@ class AdvancedTab(SettingsTab):
self.data_directory_new_label = QtWidgets.QLabel(self.data_directory_group_box)
self.data_directory_new_label.setObjectName('data_directory_current_label')
self.data_directory_path_edit = PathEdit(self.data_directory_group_box, path_type=PathType.Directories,
default_path=AppLocation.get_directory(AppLocation.DataDir))
default_path=str(AppLocation.get_directory(AppLocation.DataDir)))
self.data_directory_layout.addRow(self.data_directory_new_label, self.data_directory_path_edit)
self.new_data_directory_has_files_label = QtWidgets.QLabel(self.data_directory_group_box)
self.new_data_directory_has_files_label.setObjectName('new_data_directory_has_files_label')
@ -373,7 +373,7 @@ class AdvancedTab(SettingsTab):
self.new_data_directory_has_files_label.hide()
self.data_directory_cancel_button.hide()
# Since data location can be changed, make sure the path is present.
self.data_directory_path_edit.path = AppLocation.get_data_path()
self.data_directory_path_edit.path = str(AppLocation.get_data_path())
# Don't allow data directory move if running portable.
if settings.value('advanced/is portable'):
self.data_directory_group_box.hide()
@ -497,7 +497,7 @@ class AdvancedTab(SettingsTab):
'closed.').format(path=new_data_path),
defaultButton=QtWidgets.QMessageBox.No)
if answer != QtWidgets.QMessageBox.Yes:
self.data_directory_path_edit.path = AppLocation.get_data_path()
self.data_directory_path_edit.path = str(AppLocation.get_data_path())
return
# Check if data already exists here.
self.check_data_overwrite(new_data_path)
@ -550,7 +550,7 @@ class AdvancedTab(SettingsTab):
"""
Cancel the data directory location change
"""
self.data_directory_path_edit.path = AppLocation.get_data_path()
self.data_directory_path_edit.path = str(AppLocation.get_data_path())
self.data_directory_copy_check_box.setChecked(False)
self.main_window.set_new_data_path(None)
self.main_window.set_copy_data(False)

View File

@ -554,8 +554,8 @@ class FirstTimeForm(QtWidgets.QWizard, UiFirstTimeWizard, RegistryProperties):
"""
# Build directories for downloads
songs_destination = os.path.join(gettempdir(), 'openlp')
bibles_destination = AppLocation.get_section_data_path('bibles')
themes_destination = AppLocation.get_section_data_path('themes')
bibles_destination = str(AppLocation.get_section_data_path('bibles'))
themes_destination = str(AppLocation.get_section_data_path('themes'))
missed_files = []
# Download songs
for i in range(self.songs_list_widget.count()):

View File

@ -484,7 +484,7 @@ class MainDisplay(OpenLPMixin, Display, RegistryProperties):
service_item = ServiceItem()
service_item.title = 'webkit'
service_item.processor = 'webkit'
path = os.path.join(AppLocation.get_section_data_path('themes'),
path = os.path.join(str(AppLocation.get_section_data_path('themes')),
self.service_item.theme_data.theme_name)
service_item.add_from_command(path,
self.service_item.theme_data.background_filename,

View File

@ -305,9 +305,9 @@ class Ui_MainWindow(object):
# Give QT Extra Hint that this is an About Menu Item
self.about_item.setMenuRole(QtWidgets.QAction.AboutRole)
if is_win():
self.local_help_file = os.path.join(AppLocation.get_directory(AppLocation.AppDir), 'OpenLP.chm')
self.local_help_file = os.path.join(str(AppLocation.get_directory(AppLocation.AppDir)), 'OpenLP.chm')
elif is_macosx():
self.local_help_file = os.path.join(AppLocation.get_directory(AppLocation.AppDir),
self.local_help_file = os.path.join(str(AppLocation.get_directory(AppLocation.AppDir)),
'..', 'Resources', 'OpenLP.help')
self.user_manual_item = create_action(main_window, 'userManualItem', icon=':/system/system_help_contents.png',
can_shortcuts=True, category=UiStrings().Help,
@ -788,7 +788,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
"""
Open data folder
"""
path = AppLocation.get_data_path()
path = str(AppLocation.get_data_path())
QtGui.QDesktopServices.openUrl(QtCore.QUrl.fromLocalFile(path))
def on_update_theme_images(self):
@ -1438,7 +1438,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
settings = QtCore.QSettings()
settings.setValue('advanced/data path', self.new_data_path)
# Check if the new data path is our default.
if self.new_data_path == AppLocation.get_directory(AppLocation.DataDir):
if self.new_data_path == str(AppLocation.get_directory(AppLocation.DataDir)):
settings.remove('advanced/data path')
self.application.set_normal_cursor()

View File

@ -176,7 +176,7 @@ class PrintServiceForm(QtWidgets.QDialog, Ui_PrintServiceDialog, RegistryPropert
html_data = self._add_element('html')
self._add_element('head', parent=html_data)
self._add_element('title', self.title_line_edit.text(), html_data.head)
css_path = os.path.join(AppLocation.get_data_path(), 'serviceprint', 'service_print.css')
css_path = os.path.join(str(AppLocation.get_data_path()), 'serviceprint', 'service_print.css')
custom_css = get_text_file_string(css_path)
if not custom_css:
custom_css = DEFAULT_CSS

View File

@ -420,9 +420,7 @@ class ProjectorManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, UiProjecto
:param opt: Needed by PyQt5
"""
try:
ip = opt.link.ip
projector = opt
projector.link.set_shutter_closed()
opt.link.set_shutter_closed()
except AttributeError:
for list_item in self.projector_list_widget.selectedItems():
if list_item is None:
@ -455,9 +453,7 @@ class ProjectorManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, UiProjecto
:param opt: Needed by PyQt5
"""
try:
ip = opt.link.ip
projector = opt
projector.link.connect_to_host()
opt.link.connect_to_host()
except AttributeError:
for list_item in self.projector_list_widget.selectedItems():
if list_item is None:
@ -527,7 +523,8 @@ class ProjectorManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, UiProjecto
self.projector_list = new_list
list_item = self.projector_list_widget.takeItem(self.projector_list_widget.currentRow())
list_item = None
deleted = self.projectordb.delete_projector(projector.db_item)
if not self.projectordb.delete_projector(projector.db_item):
log.warning('Delete projector {item} failed'.format(item=projector.db_item))
for item in self.projector_list:
log.debug('New projector list - item: {ip} {name}'.format(ip=item.link.ip, name=item.link.name))
@ -538,9 +535,7 @@ class ProjectorManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, UiProjecto
:param opt: Needed by PyQt5
"""
try:
ip = opt.link.ip
projector = opt
projector.link.disconnect_from_host()
opt.link.disconnect_from_host()
except AttributeError:
for list_item in self.projector_list_widget.selectedItems():
if list_item is None:
@ -573,9 +568,7 @@ class ProjectorManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, UiProjecto
:param opt: Needed by PyQt5
"""
try:
ip = opt.link.ip
projector = opt
projector.link.set_power_off()
opt.link.set_power_off()
except AttributeError:
for list_item in self.projector_list_widget.selectedItems():
if list_item is None:
@ -593,9 +586,7 @@ class ProjectorManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, UiProjecto
:param opt: Needed by PyQt5
"""
try:
ip = opt.link.ip
projector = opt
projector.link.set_power_on()
opt.link.set_power_on()
except AttributeError:
for list_item in self.projector_list_widget.selectedItems():
if list_item is None:
@ -613,9 +604,7 @@ class ProjectorManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, UiProjecto
:param opt: Needed by PyQt5
"""
try:
ip = opt.link.ip
projector = opt
projector.link.set_shutter_open()
opt.link.set_shutter_open()
except AttributeError:
for list_item in self.projector_list_widget.selectedItems():
if list_item is None:
@ -662,9 +651,10 @@ class ProjectorManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, UiProjecto
data=translate('OpenLP.ProjectorManager', 'Closed')
if projector.link.shutter
else translate('OpenLP', 'Open'))
message = '%s<b>%s</b>: %s<br />' % (message,
translate('OpenLP.ProjectorManager', 'Current source input is'),
projector.link.source)
message = '{msg}<b>{source}</b>: {selected}<br />'.format(msg=message,
source=translate('OpenLP.ProjectorManager',
'Current source input is'),
selected=projector.link.source)
if projector.link.pjlink_class == '2':
# Information only available for PJLink Class 2 projectors
message += '<b>{title}</b>: {data}<br /><br />'.format(title=translate('OpenLP.ProjectorManager',
@ -685,10 +675,10 @@ class ProjectorManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, UiProjecto
'Lamp'),
count=count,
status=translate('OpenLP.ProjectorManager',
' is on')
'ON')
if item['On']
else translate('OpenLP.ProjectorManager',
'is off'))
'OFF'))
message += '<b>{title}</b>: {hours}<br />'.format(title=translate('OpenLP.ProjectorManager', 'Hours'),
hours=item['Hours'])

View File

@ -393,9 +393,9 @@ class SourceSelectSingle(QtWidgets.QDialog):
QtCore.Qt.WindowCloseButtonHint)
self.edit = edit
if self.edit:
title = translate('OpenLP.SourceSelectForm', 'Edit Projector Source Text')
self.title = translate('OpenLP.SourceSelectForm', 'Edit Projector Source Text')
else:
title = translate('OpenLP.SourceSelectForm', 'Select Projector Source')
self.title = translate('OpenLP.SourceSelectForm', 'Select Projector Source')
self.setObjectName('source_select_single')
self.setWindowIcon(build_icon(':/icon/openlp-log.svg'))
self.setModal(True)

View File

@ -66,6 +66,12 @@ class ServiceManagerList(QtWidgets.QTreeWidget):
elif event.key() == QtCore.Qt.Key_Down:
self.service_manager.on_move_selection_down()
event.accept()
elif event.key() == QtCore.Qt.Key_Right:
self.service_manager.on_expand_selection()
event.accept()
elif event.key() == QtCore.Qt.Key_Left:
self.service_manager.on_collapse_selection()
event.accept()
elif event.key() == QtCore.Qt.Key_Delete:
self.service_manager.on_delete_from_service()
event.accept()
@ -217,7 +223,7 @@ class Ui_ServiceManager(object):
self.service_manager_list.itemExpanded.connect(self.expanded)
# Last little bits of setting up
self.service_theme = Settings().value(self.main_window.service_manager_settings_section + '/service theme')
self.service_path = AppLocation.get_section_data_path('servicemanager')
self.service_path = str(AppLocation.get_section_data_path('servicemanager'))
# build the drag and drop context menu
self.dnd_menu = QtWidgets.QMenu()
self.new_action = self.dnd_menu.addAction(translate('OpenLP.ServiceManager', '&Add New Item'))
@ -1119,6 +1125,35 @@ class ServiceManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ServiceMa
return
self.service_manager_list.setCurrentItem(item_after)
def on_expand_selection(self):
"""
Expands cursor selection on the window. Called by the right arrow
"""
item = self.service_manager_list.currentItem()
# Since we only have 2 levels we find them by checking for children
if item.childCount():
if not self.service_manager_list.isExpanded(self.service_manager_list.currentIndex()):
self.service_manager_list.expandItem(item)
self.service_manager.expanded(item)
# If not expanded, Expand it
self.service_manager_list.setCurrentItem(self.service_manager_list.itemBelow(item))
# Then move selection down to child whether it needed to be expanded or not
def on_collapse_selection(self):
"""
Collapses cursor selection on the window Called by the left arrow
"""
item = self.service_manager_list.currentItem()
# Since we only have 2 levels we find them by checking for children
if item.childCount():
if self.service_manager_list.isExpanded(self.service_manager_list.currentIndex()):
self.service_manager_list.collapseItem(item)
self.service_manager.collapsed(item)
else: # If selection is lower level
self.service_manager_list.collapseItem(item.parent())
self.service_manager.collapsed(item.parent())
self.service_manager_list.setCurrentItem(item.parent())
def on_collapse_all(self, field=None):
"""
Collapse all the service items.

View File

@ -159,7 +159,7 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ThemeManage
"""
Set up the theme path variables
"""
self.path = AppLocation.get_section_data_path(self.settings_section)
self.path = str(AppLocation.get_section_data_path(self.settings_section))
check_directory_exists(self.path)
self.thumb_path = os.path.join(self.path, 'thumbnails')
check_directory_exists(self.thumb_path)
@ -445,7 +445,7 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ThemeManage
self.application.set_busy_cursor()
files = AppLocation.get_files(self.settings_section, '.otz')
for theme_file in files:
theme_file = os.path.join(self.path, theme_file)
theme_file = os.path.join(self.path, str(theme_file))
self.unzip_theme(theme_file, self.path)
delete_file(theme_file)
files = AppLocation.get_files(self.settings_section, '.png')
@ -470,6 +470,7 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ThemeManage
files.sort(key=lambda file_name: get_locale_key(str(file_name)))
# now process the file list of png files
for name in files:
name = str(name)
# check to see file is in theme root directory
theme = os.path.join(self.path, name)
if os.path.exists(theme):

View File

@ -584,7 +584,7 @@ class BibleImportForm(OpenLPWizard):
elif self.currentPage() == self.license_details_page:
license_version = self.field('license_version')
license_copyright = self.field('license_copyright')
path = AppLocation.get_section_data_path('bibles')
path = str(AppLocation.get_section_data_path('bibles'))
if not license_version:
critical_error_message_box(
UiStrings().EmptyField,

View File

@ -470,7 +470,7 @@ class BiblesResourcesDB(QtCore.QObject, Manager):
Return the cursor object. Instantiate one if it doesn't exist yet.
"""
if BiblesResourcesDB.cursor is None:
file_path = os.path.join(AppLocation.get_directory(AppLocation.PluginsDir),
file_path = os.path.join(str(AppLocation.get_directory(AppLocation.PluginsDir)),
'bibles', 'resources', 'bibles_resources.sqlite')
conn = sqlite3.connect(file_path)
BiblesResourcesDB.cursor = conn.cursor()
@ -759,7 +759,7 @@ class AlternativeBookNamesDB(QtCore.QObject, Manager):
"""
if AlternativeBookNamesDB.cursor is None:
file_path = os.path.join(
AppLocation.get_directory(AppLocation.DataDir), 'bibles', 'alternative_book_names.sqlite')
str(AppLocation.get_directory(AppLocation.DataDir)), 'bibles', 'alternative_book_names.sqlite')
if not os.path.exists(file_path):
# create new DB, create table alternative_book_names
AlternativeBookNamesDB.conn = sqlite3.connect(file_path)

View File

@ -325,7 +325,7 @@ class BGExtract(RegistryProperties):
returns a list in the form [(biblename, biblekey, language_code)]
"""
log.debug('BGExtract.get_bibles_from_http')
bible_url = 'https://biblegateway.com/versions/'
bible_url = 'https://www.biblegateway.com/versions/'
soup = get_soup_for_bible_ref(bible_url)
if not soup:
return None
@ -773,7 +773,7 @@ def get_soup_for_bible_ref(reference_url, header=None, pre_parse_regex=None, pre
return None
try:
page = get_web_page(reference_url, header, True)
except:
except Exception as e:
page = None
if not page:
send_error_message('download')

View File

@ -111,7 +111,7 @@ class BibleManager(OpenLPMixin, RegistryProperties):
self.settings_section = 'bibles'
self.web = 'Web'
self.db_cache = None
self.path = AppLocation.get_section_data_path(self.settings_section)
self.path = str(AppLocation.get_section_data_path(self.settings_section))
self.proxy_name = Settings().value(self.settings_section + '/proxy name')
self.suffix = '.sqlite'
self.import_wizard = None
@ -124,7 +124,7 @@ class BibleManager(OpenLPMixin, RegistryProperties):
of HTTPBible is loaded instead of the BibleDB class.
"""
log.debug('Reload bibles')
files = AppLocation.get_files(self.settings_section, self.suffix)
files = [str(file) for file in AppLocation.get_files(self.settings_section, self.suffix)]
if 'alternative_book_names.sqlite' in files:
files.remove('alternative_book_names.sqlite')
log.debug('Bible Files {text}'.format(text=files))

View File

@ -98,7 +98,7 @@ class ImageMediaItem(MediaManagerItem):
self.list_view.setIconSize(QtCore.QSize(88, 50))
self.list_view.setIndentation(self.list_view.default_indentation)
self.list_view.allow_internal_dnd = True
self.service_path = os.path.join(AppLocation.get_section_data_path(self.settings_section), 'thumbnails')
self.service_path = os.path.join(str(AppLocation.get_section_data_path(self.settings_section)), 'thumbnails')
check_directory_exists(self.service_path)
# Load images from the database
self.load_full_list(

View File

@ -300,7 +300,7 @@ class MediaMediaItem(MediaManagerItem, RegistryProperties):
Initialize media item.
"""
self.list_view.clear()
self.service_path = os.path.join(AppLocation.get_section_data_path(self.settings_section), 'thumbnails')
self.service_path = os.path.join(str(AppLocation.get_section_data_path(self.settings_section)), 'thumbnails')
check_directory_exists(self.service_path)
self.load_list(Settings().value(self.settings_section + '/media files'))
self.rebuild_players()

View File

@ -75,7 +75,7 @@ class MediaPlugin(Plugin):
exists = process_check_binary('mediainfo')
# If mediainfo is not in the path, try to find it in the application folder
if not exists:
exists = process_check_binary(os.path.join(AppLocation.get_directory(AppLocation.AppDir), 'mediainfo'))
exists = process_check_binary(os.path.join(str(AppLocation.get_directory(AppLocation.AppDir)), 'mediainfo'))
return exists
def app_startup(self):

View File

@ -122,10 +122,10 @@ class PdfController(PresentationController):
self.mutoolbin = pdf_program
else:
# Fallback to autodetection
application_path = AppLocation.get_directory(AppLocation.AppDir)
application_path = str(AppLocation.get_directory(AppLocation.AppDir))
if is_win():
# for windows we only accept mudraw.exe or mutool.exe in the base folder
application_path = AppLocation.get_directory(AppLocation.AppDir)
application_path = str(AppLocation.get_directory(AppLocation.AppDir))
if os.path.isfile(os.path.join(application_path, 'mudraw.exe')):
self.mudrawbin = os.path.join(application_path, 'mudraw.exe')
elif os.path.isfile(os.path.join(application_path, 'mutool.exe')):
@ -142,7 +142,7 @@ class PdfController(PresentationController):
self.gsbin = which('gs')
# Last option: check if mudraw or mutool is placed in OpenLP base folder
if not self.mudrawbin and not self.mutoolbin and not self.gsbin:
application_path = AppLocation.get_directory(AppLocation.AppDir)
application_path = str(AppLocation.get_directory(AppLocation.AppDir))
if os.path.isfile(os.path.join(application_path, 'mudraw')):
self.mudrawbin = os.path.join(application_path, 'mudraw')
elif os.path.isfile(os.path.join(application_path, 'mutool')):
@ -199,8 +199,8 @@ class PdfDocument(PresentationDocument):
:return: The resolution dpi to be used.
"""
# Use a postscript script to get size of the pdf. It is assumed that all pages have same size
gs_resolution_script = AppLocation.get_directory(
AppLocation.PluginsDir) + '/presentations/lib/ghostscript_get_resolution.ps'
gs_resolution_script = str(AppLocation.get_directory(
AppLocation.PluginsDir)) + '/presentations/lib/ghostscript_get_resolution.ps'
# Run the script on the pdf to get the size
runlog = []
try:

View File

@ -85,7 +85,7 @@ class PptviewController(PresentationController):
if self.process:
return
log.debug('start PPTView')
dll_path = os.path.join(AppLocation.get_directory(AppLocation.AppDir),
dll_path = os.path.join(str(AppLocation.get_directory(AppLocation.AppDir)),
'plugins', 'presentations', 'lib', 'pptviewlib', 'pptviewlib.dll')
self.process = cdll.LoadLibrary(dll_path)
if log.isEnabledFor(logging.DEBUG):

View File

@ -415,8 +415,9 @@ class PresentationController(object):
self.document_class = document_class
self.settings_section = self.plugin.settings_section
self.available = None
self.temp_folder = os.path.join(AppLocation.get_section_data_path(self.settings_section), name)
self.thumbnail_folder = os.path.join(AppLocation.get_section_data_path(self.settings_section), 'thumbnails')
self.temp_folder = os.path.join(str(AppLocation.get_section_data_path(self.settings_section)), name)
self.thumbnail_folder = os.path.join(
str(AppLocation.get_section_data_path(self.settings_section)), 'thumbnails')
self.thumbnail_prefix = 'slide'
check_directory_exists(self.thumbnail_folder)
check_directory_exists(self.temp_folder)

View File

@ -171,8 +171,8 @@ class HttpRouter(RegistryProperties):
]
self.settings_section = 'remotes'
self.translate()
self.html_dir = os.path.join(AppLocation.get_directory(AppLocation.PluginsDir), 'remotes', 'html')
self.config_dir = os.path.join(AppLocation.get_data_path(), 'stages')
self.html_dir = os.path.join(str(AppLocation.get_directory(AppLocation.PluginsDir)), 'remotes', 'html')
self.config_dir = os.path.join(str(AppLocation.get_data_path()), 'stages')
def do_post_processor(self):
"""
@ -456,7 +456,7 @@ class HttpRouter(RegistryProperties):
if controller_name in supported_controllers:
full_path = urllib.parse.unquote(file_name)
if '..' not in full_path: # no hacking please
full_path = os.path.normpath(os.path.join(AppLocation.get_section_data_path(controller_name),
full_path = os.path.normpath(os.path.join(str(AppLocation.get_section_data_path(controller_name)),
'thumbnails/' + full_path))
if os.path.exists(full_path):
path, just_file_name = os.path.split(full_path)
@ -565,7 +565,7 @@ class HttpRouter(RegistryProperties):
elif current_item.is_image() and not frame.get('image', '') and Settings().value('remotes/thumbnails'):
item['tag'] = str(index + 1)
thumbnail_path = os.path.join('images', 'thumbnails', frame['title'])
full_thumbnail_path = os.path.join(AppLocation.get_data_path(), thumbnail_path)
full_thumbnail_path = os.path.join(str(AppLocation.get_data_path()), thumbnail_path)
# Create thumbnail if it doesn't exists
if not os.path.exists(full_thumbnail_path):
create_thumb(current_item.get_frame_path(index), full_thumbnail_path, False)
@ -582,7 +582,7 @@ class HttpRouter(RegistryProperties):
if current_item.is_capable(ItemCapabilities.HasThumbnails) and \
Settings().value('remotes/thumbnails'):
# If the file is under our app directory tree send the portion after the match
data_path = AppLocation.get_data_path()
data_path = str(AppLocation.get_data_path())
if frame['image'][0:len(data_path)] == data_path:
item['img'] = urllib.request.pathname2url(frame['image'][len(data_path):])
item['text'] = str(frame['title'])

View File

@ -1065,7 +1065,7 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
self.manager.save_object(self.song)
audio_files = [a.file_name for a in self.song.media_files]
log.debug(audio_files)
save_path = os.path.join(AppLocation.get_section_data_path(self.media_item.plugin.name), 'audio',
save_path = os.path.join(str(AppLocation.get_section_data_path(self.media_item.plugin.name)), 'audio',
str(self.song.id))
check_directory_exists(save_path)
self.song.media_files = []

View File

@ -538,7 +538,7 @@ def delete_song(song_id, song_plugin):
except OSError:
log.exception('Could not remove file: {name}'.format(name=media_file.file_name))
try:
save_path = os.path.join(AppLocation.get_section_data_path(song_plugin.name), 'audio', str(song_id))
save_path = os.path.join(str(AppLocation.get_section_data_path(song_plugin.name)), 'audio', str(song_id))
if os.path.exists(save_path):
os.rmdir(save_path)
except OSError:

View File

@ -421,7 +421,7 @@ class SongImport(QtCore.QObject):
:param filename: The file to copy.
"""
if not hasattr(self, 'save_path'):
self.save_path = os.path.join(AppLocation.get_section_data_path(self.import_wizard.plugin.name),
self.save_path = os.path.join(str(AppLocation.get_section_data_path(self.import_wizard.plugin.name)),
'audio', str(song_id))
check_directory_exists(self.save_path)
if not filename.startswith(self.save_path):

View File

@ -88,9 +88,9 @@ class SongMediaItem(MediaManagerItem):
song.media_files = []
for i, bga in enumerate(item.background_audio):
dest_file = os.path.join(
AppLocation.get_section_data_path(self.plugin.name), 'audio', str(song.id), os.path.split(bga)[1])
str(AppLocation.get_section_data_path(self.plugin.name)), 'audio', str(song.id), os.path.split(bga)[1])
check_directory_exists(os.path.split(dest_file)[0])
shutil.copyfile(os.path.join(AppLocation.get_section_data_path('servicemanager'), bga), dest_file)
shutil.copyfile(os.path.join(str(AppLocation.get_section_data_path('servicemanager')), bga), dest_file)
song.media_files.append(MediaFile.populate(weight=i, file_name=dest_file))
self.plugin.manager.save_object(song, True)
@ -533,7 +533,8 @@ class SongMediaItem(MediaManagerItem):
'copy', 'For song cloning'))
# Copy audio files from the old to the new song
if len(old_song.media_files) > 0:
save_path = os.path.join(AppLocation.get_section_data_path(self.plugin.name), 'audio', str(new_song.id))
save_path = os.path.join(
str(AppLocation.get_section_data_path(self.plugin.name)), 'audio', str(new_song.id))
check_directory_exists(save_path)
for media_file in old_song.media_files:
new_media_file_name = os.path.join(save_path, os.path.basename(media_file.file_name))

View File

@ -1,4 +1,14 @@
# E402 module level import not at top of file
# E722 do not use bare except, specify exception instead
# F841 local variable '<variable>' is assigned to but never used
[pep8]
exclude=resources.py,vlc.py
max-line-length = 120
ignore = E402,E722
ignore = E402
[flake8]
exclude=resources.py,vlc.py
max-line-length = 120
ignore = E402

View File

@ -24,8 +24,9 @@ Functional tests to test the AppLocation class and related methods.
"""
import copy
import os
from pathlib import Path
from unittest import TestCase
from unittest.mock import patch
from unittest.mock import MagicMock, patch
from openlp.core.common import AppLocation, get_frozen_path
@ -64,56 +65,51 @@ class TestAppLocation(TestCase):
"""
Test the AppLocation.get_data_path() method when a custom location is set in the settings
"""
with patch('openlp.core.common.applocation.Settings') as mocked_class,\
patch('openlp.core.common.applocation.os') as mocked_os:
# GIVEN: A mocked out Settings class which returns a custom data location
mocked_settings = mocked_class.return_value
mocked_settings.contains.return_value = True
mocked_settings.value.return_value.toString.return_value = 'custom/dir'
mocked_os.path.normpath.return_value = 'custom/dir'
# GIVEN: A mocked out Settings class which returns a custom data location
mocked_settings_instance = MagicMock(
**{'contains.return_value': True, 'value.return_value': Path('custom', 'dir')})
with patch('openlp.core.common.applocation.Settings', return_value=mocked_settings_instance):
# WHEN: we call AppLocation.get_data_path()
data_path = AppLocation.get_data_path()
# THEN: the mocked Settings methods were called and the value returned was our set up value
mocked_settings.contains.assert_called_with('advanced/data path')
mocked_settings.value.assert_called_with('advanced/data path')
self.assertEqual('custom/dir', data_path, 'Result should be "custom/dir"')
mocked_settings_instance.contains.assert_called_with('advanced/data path')
mocked_settings_instance.value.assert_called_with('advanced/data path')
self.assertEqual(Path('custom', 'dir'), data_path, 'Result should be "custom/dir"')
def test_get_files_no_section_no_extension(self):
"""
Test the AppLocation.get_files() method with no parameters passed.
"""
with patch('openlp.core.common.AppLocation.get_data_path') as mocked_get_data_path, \
patch('openlp.core.common.applocation.os.listdir') as mocked_listdir:
# GIVEN: Our mocked modules/methods.
mocked_get_data_path.return_value = 'test/dir'
mocked_listdir.return_value = copy.deepcopy(FILE_LIST)
# GIVEN: Our mocked modules/methods.
with patch.object(Path, 'glob', return_value=[Path('/dir/file5.mp3'), Path('/dir/file6.mp3')]) as mocked_glob, \
patch('openlp.core.common.AppLocation.get_data_path', return_value=Path('/dir')):
# When: Get the list of files.
result = AppLocation.get_files()
# Then: Check if the section parameter was used correctly, and the glob argument was passed.
mocked_glob.assert_called_once_with('*')
# Then: check if the file lists are identical.
self.assertListEqual(FILE_LIST, result, 'The file lists should be identical.')
self.assertListEqual([Path('file5.mp3'), Path('file6.mp3')], result, 'The file lists should be identical.')
def test_get_files(self):
"""
Test the AppLocation.get_files() method with all parameters passed.
"""
with patch('openlp.core.common.AppLocation.get_data_path') as mocked_get_data_path, \
patch('openlp.core.common.applocation.os.listdir') as mocked_listdir:
# GIVEN: Our mocked modules/methods.
mocked_get_data_path.return_value = os.path.join('test', 'dir')
mocked_listdir.return_value = copy.deepcopy(FILE_LIST)
# GIVEN: Our mocked modules/methods.
with patch.object(Path, 'glob', return_value=[Path('/dir/section/file5.mp3'), Path('/dir/section/file6.mp3')]) \
as mocked_glob, \
patch('openlp.core.common.AppLocation.get_data_path', return_value=Path('/dir')):
# When: Get the list of files.
result = AppLocation.get_files('section', '.mp3')
# Then: Check if the section parameter was used correctly.
mocked_listdir.assert_called_with(os.path.join('test', 'dir', 'section'))
# Then: check if the file lists are identical.
self.assertListEqual(['file5.mp3', 'file6.mp3'], result, 'The file lists should be identical.')
# Then: The section parameter was used correctly, and the glob argument was passed..
mocked_glob.assert_called_once_with('*.mp3')
self.assertListEqual([Path('file5.mp3'), Path('file6.mp3')], result, 'The file lists should be identical.')
def test_get_section_data_path(self):
"""
@ -122,7 +118,7 @@ class TestAppLocation(TestCase):
with patch('openlp.core.common.AppLocation.get_data_path') as mocked_get_data_path, \
patch('openlp.core.common.applocation.check_directory_exists') as mocked_check_directory_exists:
# GIVEN: A mocked out AppLocation.get_data_path()
mocked_get_data_path.return_value = os.path.join('test', 'dir')
mocked_get_data_path.return_value = Path('test', 'dir')
mocked_check_directory_exists.return_value = True
# WHEN: we call AppLocation.get_data_path()
@ -130,7 +126,7 @@ class TestAppLocation(TestCase):
# THEN: check that all the correct methods were called, and the result is correct
mocked_check_directory_exists.assert_called_with(os.path.join('test', 'dir', 'section'))
self.assertEqual(os.path.join('test', 'dir', 'section'), data_path, 'Result should be "test/dir/section"')
self.assertEqual(Path('test', 'dir', 'section'), data_path, 'Result should be "test/dir/section"')
def test_get_directory_for_app_dir(self):
"""
@ -138,13 +134,13 @@ class TestAppLocation(TestCase):
"""
# GIVEN: A mocked out _get_frozen_path function
with patch('openlp.core.common.applocation.get_frozen_path') as mocked_get_frozen_path:
mocked_get_frozen_path.return_value = os.path.join('app', 'dir')
mocked_get_frozen_path.return_value = Path('app', 'dir')
# WHEN: We call AppLocation.get_directory
directory = AppLocation.get_directory(AppLocation.AppDir)
# THEN: check that the correct directory is returned
self.assertEqual(os.path.join('app', 'dir'), directory, 'Directory should be "app/dir"')
self.assertEqual(Path('app', 'dir'), directory, 'Directory should be "app/dir"')
def test_get_directory_for_plugins_dir(self):
"""
@ -157,7 +153,7 @@ class TestAppLocation(TestCase):
patch('openlp.core.common.applocation.sys') as mocked_sys:
mocked_abspath.return_value = os.path.join('plugins', 'dir')
mocked_split.return_value = ['openlp']
mocked_get_frozen_path.return_value = os.path.join('plugins', 'dir')
mocked_get_frozen_path.return_value = Path('dir')
mocked_sys.frozen = 1
mocked_sys.argv = ['openlp']
@ -165,7 +161,7 @@ class TestAppLocation(TestCase):
directory = AppLocation.get_directory(AppLocation.PluginsDir)
# THEN: The correct directory should be returned
self.assertEqual(os.path.join('plugins', 'dir'), directory, 'Directory should be "plugins/dir"')
self.assertEqual(Path('dir', 'plugins'), directory, 'Directory should be "dir/plugins"')
def test_get_frozen_path_in_unfrozen_app(self):
"""

View File

@ -79,7 +79,7 @@ class TestCommonFunctions(TestCase):
Test the `extension_loader` function when no files are found
"""
# GIVEN: A mocked `Path.glob` method which does not match any files
with patch('openlp.core.common.AppLocation.get_directory', return_value='/app/dir/openlp'), \
with patch('openlp.core.common.AppLocation.get_directory', return_value=Path('/', 'app', 'dir', 'openlp')), \
patch.object(common.Path, 'glob', return_value=[]), \
patch('openlp.core.common.importlib.import_module') as mocked_import_module:
@ -94,11 +94,12 @@ class TestCommonFunctions(TestCase):
Test the `extension_loader` function when it successfully finds and loads some files
"""
# GIVEN: A mocked `Path.glob` method which returns a list of files
with patch('openlp.core.common.AppLocation.get_directory', return_value='/app/dir/openlp'), \
patch.object(common.Path, 'glob', return_value=[Path('/app/dir/openlp/import_dir/file1.py'),
Path('/app/dir/openlp/import_dir/file2.py'),
Path('/app/dir/openlp/import_dir/file3.py'),
Path('/app/dir/openlp/import_dir/file4.py')]), \
with patch('openlp.core.common.AppLocation.get_directory', return_value=Path('/', 'app', 'dir', 'openlp')), \
patch.object(common.Path, 'glob', return_value=[
Path('/', 'app', 'dir', 'openlp', 'import_dir', 'file1.py'),
Path('/', 'app', 'dir', 'openlp', 'import_dir', 'file2.py'),
Path('/', 'app', 'dir', 'openlp', 'import_dir', 'file3.py'),
Path('/', 'app', 'dir', 'openlp', 'import_dir', 'file4.py')]), \
patch('openlp.core.common.importlib.import_module') as mocked_import_module:
# WHEN: Calling `extension_loader` with a list of files to exclude
@ -113,8 +114,9 @@ class TestCommonFunctions(TestCase):
Test the `extension_loader` function when `SourceFileLoader` raises a `ImportError`
"""
# GIVEN: A mocked `import_module` which raises an `ImportError`
with patch('openlp.core.common.AppLocation.get_directory', return_value='/app/dir/openlp'), \
patch.object(common.Path, 'glob', return_value=[Path('/app/dir/openlp/import_dir/file1.py')]), \
with patch('openlp.core.common.AppLocation.get_directory', return_value=Path('/', 'app', 'dir', 'openlp')), \
patch.object(common.Path, 'glob', return_value=[
Path('/', 'app', 'dir', 'openlp', 'import_dir', 'file1.py')]), \
patch('openlp.core.common.importlib.import_module', side_effect=ImportError()), \
patch('openlp.core.common.log') as mocked_logger:
@ -129,8 +131,9 @@ class TestCommonFunctions(TestCase):
Test the `extension_loader` function when `import_module` raises a `ImportError`
"""
# GIVEN: A mocked `SourceFileLoader` which raises an `OSError`
with patch('openlp.core.common.AppLocation.get_directory', return_value='/app/dir/openlp'), \
patch.object(common.Path, 'glob', return_value=[Path('/app/dir/openlp/import_dir/file1.py')]), \
with patch('openlp.core.common.AppLocation.get_directory', return_value=Path('/', 'app', 'dir', 'openlp')), \
patch.object(common.Path, 'glob', return_value=[
Path('/', 'app', 'dir', 'openlp', 'import_dir', 'file1.py')]), \
patch('openlp.core.common.importlib.import_module', side_effect=OSError()), \
patch('openlp.core.common.log') as mocked_logger:

View File

@ -0,0 +1,88 @@
# -*- 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.path package.
"""
import os
from pathlib import Path
from unittest import TestCase
from openlp.core.lib.path import path_to_str, str_to_path
class TestPath(TestCase):
"""
Tests for the :mod:`openlp.core.lib.path` module
"""
def test_path_to_str_type_error(self):
"""
Test that `path_to_str` raises a type error when called with an invalid type
"""
# GIVEN: The `path_to_str` function
# WHEN: Calling `path_to_str` with an invalid Type
# THEN: A TypeError should have been raised
with self.assertRaises(TypeError):
path_to_str(str())
def test_path_to_str_none(self):
"""
Test that `path_to_str` correctly converts the path parameter when passed with None
"""
# GIVEN: The `path_to_str` function
# WHEN: Calling the `path_to_str` function with None
result = path_to_str(None)
# THEN: `path_to_str` should return an empty string
self.assertEqual(result, '')
def test_path_to_str_path_object(self):
"""
Test that `path_to_str` correctly converts the path parameter when passed a Path object
"""
# GIVEN: The `path_to_str` function
# WHEN: Calling the `path_to_str` function with a Path object
result = path_to_str(Path('test/path'))
# THEN: `path_to_str` should return a string representation of the Path object
self.assertEqual(result, os.path.join('test', 'path'))
def test_str_to_path_type_error(self):
"""
Test that `str_to_path` raises a type error when called with an invalid type
"""
# GIVEN: The `str_to_path` function
# WHEN: Calling `str_to_path` with an invalid Type
# THEN: A TypeError should have been raised
with self.assertRaises(TypeError):
str_to_path(Path())
def test_str_to_path_empty_str(self):
"""
Test that `str_to_path` correctly converts the string parameter when passed with and empty string
"""
# GIVEN: The `str_to_path` function
# WHEN: Calling the `str_to_path` function with None
result = str_to_path('')
# THEN: `path_to_str` should return None
self.assertEqual(result, None)

View File

@ -59,9 +59,101 @@ class TestPJLink(TestCase):
self.assertTrue(mock_qmd5_hash.called_with(TEST_PIN,
"Connection request should have been called with TEST_PIN"))
def test_projector_class(self):
def test_projector_process_rfil_save(self):
"""
Test class version from projector
Test saving filter type
"""
# GIVEN: Test object
pjlink = pjlink_test
pjlink.model_filter = None
filter_model = 'Filter Type Test'
# WHEN: Filter model is received
pjlink.process_rfil(data=filter_model)
# THEN: Filter model number should be saved
self.assertEqual(pjlink.model_filter, filter_model, 'Filter type should have been saved')
def test_projector_process_rfil_nosave(self):
"""
Test saving filter type previously saved
"""
# GIVEN: Test object
pjlink = pjlink_test
pjlink.model_filter = 'Old filter type'
filter_model = 'Filter Type Test'
# WHEN: Filter model is received
pjlink.process_rfil(data=filter_model)
# THEN: Filter model number should be saved
self.assertNotEquals(pjlink.model_filter, filter_model, 'Filter type should NOT have been saved')
def test_projector_process_rlmp_save(self):
"""
Test saving lamp type
"""
# GIVEN: Test object
pjlink = pjlink_test
pjlink.model_lamp = None
lamp_model = 'Lamp Type Test'
# WHEN: Filter model is received
pjlink.process_rlmp(data=lamp_model)
# THEN: Filter model number should be saved
self.assertEqual(pjlink.model_lamp, lamp_model, 'Lamp type should have been saved')
def test_projector_process_rlmp_nosave(self):
"""
Test saving lamp type previously saved
"""
# GIVEN: Test object
pjlink = pjlink_test
pjlink.model_lamp = 'Old lamp type'
lamp_model = 'Filter Type Test'
# WHEN: Filter model is received
pjlink.process_rlmp(data=lamp_model)
# THEN: Filter model number should be saved
self.assertNotEquals(pjlink.model_lamp, lamp_model, 'Lamp type should NOT have been saved')
def test_projector_process_snum_set(self):
"""
Test saving serial number from projector
"""
# GIVEN: Test object
pjlink = pjlink_test
pjlink.serial_no = None
test_number = 'Test Serial Number'
# WHEN: No serial number is set and we receive serial number command
pjlink.process_snum(data=test_number)
# THEN: Serial number should be set
self.assertEqual(pjlink.serial_no, test_number,
'Projector serial number should have been set')
def test_projector_process_snum_different(self):
"""
Test projector serial number different than saved serial number
"""
# GIVEN: Test object
pjlink = pjlink_test
pjlink.serial_no = 'Previous serial number'
test_number = 'Test Serial Number'
# WHEN: No serial number is set and we receive serial number command
pjlink.process_snum(data=test_number)
# THEN: Serial number should be set
self.assertNotEquals(pjlink.serial_no, test_number,
'Projector serial number should NOT have been set')
def test_projector_clss_one(self):
"""
Test class 1 sent from projector
"""
# GIVEN: Test object
pjlink = pjlink_test
@ -70,12 +162,26 @@ class TestPJLink(TestCase):
pjlink.process_clss('1')
# THEN: Projector class should be set to 1
self.assertEquals(pjlink.pjlink_class, '1',
'Projector should have returned class=1')
self.assertEqual(pjlink.pjlink_class, '1',
'Projector should have returned class=1')
def test_non_standard_class_reply(self):
def test_projector_clss_two(self):
"""
Bugfix 1550891: CLSS request returns non-standard 'Class N' reply
Test class 2 sent from projector
"""
# GIVEN: Test object
pjlink = pjlink_test
# WHEN: Process class response
pjlink.process_clss('2')
# THEN: Projector class should be set to 1
self.assertEqual(pjlink.pjlink_class, '2',
'Projector should have returned class=2')
def test_bug_1550891_non_standard_class_reply(self):
"""
Bugfix 1550891: CLSS request returns non-standard reply
"""
# GIVEN: Test object
pjlink = pjlink_test
@ -84,8 +190,8 @@ class TestPJLink(TestCase):
pjlink.process_clss('Class 1')
# THEN: Projector class should be set with proper value
self.assertEquals(pjlink.pjlink_class, '1',
'Non-standard class reply should have set proper class')
self.assertEqual(pjlink.pjlink_class, '1',
'Non-standard class reply should have set class=1')
@patch.object(pjlink_test, 'change_status')
def test_status_change(self, mock_change_status):
@ -131,10 +237,10 @@ class TestPJLink(TestCase):
pjlink.process_command('LAMP', '22222 1')
# THEN: Lamp should have been set with status=ON and hours=22222
self.assertEquals(pjlink.lamp[0]['On'], True,
'Lamp power status should have been set to TRUE')
self.assertEquals(pjlink.lamp[0]['Hours'], 22222,
'Lamp hours should have been set to 22222')
self.assertEqual(pjlink.lamp[0]['On'], True,
'Lamp power status should have been set to TRUE')
self.assertEqual(pjlink.lamp[0]['Hours'], 22222,
'Lamp hours should have been set to 22222')
@patch.object(pjlink_test, 'projectorReceivedData')
def test_projector_process_multiple_lamp(self, mock_projectorReceivedData):
@ -148,20 +254,20 @@ class TestPJLink(TestCase):
pjlink.process_command('LAMP', '11111 1 22222 0 33333 1')
# THEN: Lamp should have been set with proper lamp status
self.assertEquals(len(pjlink.lamp), 3,
'Projector should have 3 lamps specified')
self.assertEquals(pjlink.lamp[0]['On'], True,
'Lamp 1 power status should have been set to TRUE')
self.assertEquals(pjlink.lamp[0]['Hours'], 11111,
'Lamp 1 hours should have been set to 11111')
self.assertEquals(pjlink.lamp[1]['On'], False,
'Lamp 2 power status should have been set to FALSE')
self.assertEquals(pjlink.lamp[1]['Hours'], 22222,
'Lamp 2 hours should have been set to 22222')
self.assertEquals(pjlink.lamp[2]['On'], True,
'Lamp 3 power status should have been set to TRUE')
self.assertEquals(pjlink.lamp[2]['Hours'], 33333,
'Lamp 3 hours should have been set to 33333')
self.assertEqual(len(pjlink.lamp), 3,
'Projector should have 3 lamps specified')
self.assertEqual(pjlink.lamp[0]['On'], True,
'Lamp 1 power status should have been set to TRUE')
self.assertEqual(pjlink.lamp[0]['Hours'], 11111,
'Lamp 1 hours should have been set to 11111')
self.assertEqual(pjlink.lamp[1]['On'], False,
'Lamp 2 power status should have been set to FALSE')
self.assertEqual(pjlink.lamp[1]['Hours'], 22222,
'Lamp 2 hours should have been set to 22222')
self.assertEqual(pjlink.lamp[2]['On'], True,
'Lamp 3 power status should have been set to TRUE')
self.assertEqual(pjlink.lamp[2]['Hours'], 33333,
'Lamp 3 hours should have been set to 33333')
@patch.object(pjlink_test, 'projectorReceivedData')
@patch.object(pjlink_test, 'projectorUpdateIcons')
@ -182,9 +288,9 @@ class TestPJLink(TestCase):
pjlink.process_command('POWR', PJLINK_POWR_STATUS[S_ON])
# THEN: Power should be set to ON
self.assertEquals(pjlink.power, S_ON, 'Power should have been set to ON')
self.assertEqual(pjlink.power, S_ON, 'Power should have been set to ON')
mock_send_command.assert_called_once_with('INST')
self.assertEquals(mock_UpdateIcons.emit.called, True, 'projectorUpdateIcons should have been called')
self.assertEqual(mock_UpdateIcons.emit.called, True, 'projectorUpdateIcons should have been called')
@patch.object(pjlink_test, 'projectorReceivedData')
@patch.object(pjlink_test, 'projectorUpdateIcons')
@ -205,9 +311,9 @@ class TestPJLink(TestCase):
pjlink.process_command('POWR', PJLINK_POWR_STATUS[S_STANDBY])
# THEN: Power should be set to STANDBY
self.assertEquals(pjlink.power, S_STANDBY, 'Power should have been set to STANDBY')
self.assertEquals(mock_send_command.called, False, 'send_command should not have been called')
self.assertEquals(mock_UpdateIcons.emit.called, True, 'projectorUpdateIcons should have been called')
self.assertEqual(pjlink.power, S_STANDBY, 'Power should have been set to STANDBY')
self.assertEqual(mock_send_command.called, False, 'send_command should not have been called')
self.assertEqual(mock_UpdateIcons.emit.called, True, 'projectorUpdateIcons should have been called')
@patch.object(pjlink_test, 'projectorUpdateIcons')
def test_projector_process_avmt_closed_unmuted(self, mock_projectorReceivedData):
@ -289,7 +395,7 @@ class TestPJLink(TestCase):
pjlink.process_inpt('1')
# THEN: Input selected should reflect current input
self.assertEquals(pjlink.source, '1', 'Input source should be set to "1"')
self.assertEqual(pjlink.source, '1', 'Input source should be set to "1"')
def test_projector_reset_information(self):
"""
@ -318,7 +424,7 @@ class TestPJLink(TestCase):
pjlink.reset_information()
# THEN: All information should be reset and timers stopped
self.assertEquals(pjlink.power, S_OFF, 'Projector power should be OFF')
self.assertEqual(pjlink.power, S_OFF, 'Projector power should be OFF')
self.assertIsNone(pjlink.pjlink_name, 'Projector pjlink_name should be None')
self.assertIsNone(pjlink.manufacturer, 'Projector manufacturer should be None')
self.assertIsNone(pjlink.model, 'Projector model should be None')
@ -329,7 +435,7 @@ class TestPJLink(TestCase):
self.assertIsNone(pjlink.source_available, 'Projector source_available should be None')
self.assertIsNone(pjlink.source, 'Projector source should be None')
self.assertIsNone(pjlink.other_info, 'Projector other_info should be None')
self.assertEquals(pjlink.send_queue, [], 'Projector send_queue should be an empty list')
self.assertEqual(pjlink.send_queue, [], 'Projector send_queue should be an empty list')
self.assertFalse(pjlink.send_busy, 'Projector send_busy should be False')
self.assertTrue(mock_timer.called, 'Projector timer.stop() should have been called')
self.assertTrue(mock_socket_timer.called, 'Projector socket_timer.stop() should have been called')
@ -355,8 +461,8 @@ class TestPJLink(TestCase):
# 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.name)
# THEN: 'No Authentication' signal should have been sent
mock_authentication.emit.assert_called_with(pjlink.ip)
@patch.object(pjlink_test, 'waitForReadyRead')
@patch.object(pjlink_test, 'state')
@ -381,8 +487,8 @@ class TestPJLink(TestCase):
pjlink.check_login(data=TEST_CONNECT_AUTHENTICATE)
# THEN: send_command should have the proper authentication
self.assertEquals("{test}".format(test=mock_send_command.call_args),
"call(data='{hash}%1CLSS ?\\r')".format(hash=TEST_HASH))
self.assertEqual("{test}".format(test=mock_send_command.call_args),
"call(data='{hash}%1CLSS ?\\r')".format(hash=TEST_HASH))
@patch.object(pjlink_test, 'disconnect_from_host')
def socket_abort_test(self, mock_disconnect):

View File

@ -28,9 +28,13 @@ from unittest.mock import MagicMock, patch
from openlp.core.common import Registry
from openlp.core.lib import ScreenList, ServiceItem, ItemCapabilities
from openlp.core.ui.mainwindow import MainWindow
from openlp.core.ui.servicemanager import ServiceManagerList
from openlp.core.lib.serviceitem import ServiceItem
from tests.helpers.testmixin import TestMixin
from PyQt5 import QtCore, QtGui, QtWidgets
class TestServiceManager(TestCase, TestMixin):
@ -351,3 +355,141 @@ class TestServiceManager(TestCase, TestMixin):
new_service.trigger()
assert mocked_event.call_count == 1, 'The on_new_service_clicked method should have been called once'
def test_expand_selection_on_right_arrow(self):
"""
Test that a right arrow key press event calls the on_expand_selection function
"""
# GIVEN a mocked expand function
self.service_manager.on_expand_selection = MagicMock()
# WHEN the right arrow key event is called
self.service_manager.setup_ui(self.service_manager)
event = QtGui.QKeyEvent(QtCore.QEvent.KeyPress, QtCore.Qt.Key_Right, QtCore.Qt.NoModifier)
self.service_manager.service_manager_list.keyPressEvent(event)
# THEN the on_expand_selection function should have been called.
self.service_manager.on_expand_selection.assert_called_once_with()
def test_collapse_selection_on_left_arrow(self):
"""
Test that a left arrow key press event calls the on_collapse_selection function
"""
# GIVEN a mocked collapse function
self.service_manager.on_collapse_selection = MagicMock()
# WHEN the left arrow key event is called
self.service_manager.setup_ui(self.service_manager)
event = QtGui.QKeyEvent(QtCore.QEvent.KeyPress, QtCore.Qt.Key_Left, QtCore.Qt.NoModifier)
self.service_manager.service_manager_list.keyPressEvent(event)
# THEN the on_collapse_selection function should have been called.
self.service_manager.on_collapse_selection.assert_called_once_with()
def test_move_selection_down_on_down_arrow(self):
"""
Test that a down arrow key press event calls the on_move_selection_down function
"""
# GIVEN a mocked move down function
self.service_manager.on_move_selection_down = MagicMock()
# WHEN the down arrow key event is called
self.service_manager.setup_ui(self.service_manager)
event = QtGui.QKeyEvent(QtCore.QEvent.KeyPress, QtCore.Qt.Key_Down, QtCore.Qt.NoModifier)
self.service_manager.service_manager_list.keyPressEvent(event)
# THEN the on_move_selection_down function should have been called.
self.service_manager.on_move_selection_down.assert_called_once_with()
def test_move_selection_up_on_up_arrow(self):
"""
Test that an up arrow key press event calls the on_move_selection_up function
"""
# GIVEN a mocked move up function
self.service_manager.on_move_selection_up = MagicMock()
# WHEN the up arrow key event is called
self.service_manager.setup_ui(self.service_manager)
event = QtGui.QKeyEvent(QtCore.QEvent.KeyPress, QtCore.Qt.Key_Up, QtCore.Qt.NoModifier)
self.service_manager.service_manager_list.keyPressEvent(event)
# THEN the on_move_selection_up function should have been called.
self.service_manager.on_move_selection_up.assert_called_once_with()
def _setup_service_manager_list(self):
self.service_manager.expanded = MagicMock()
self.service_manager.collapsed = MagicMock()
verse_1 = QtWidgets.QTreeWidgetItem(0)
verse_2 = QtWidgets.QTreeWidgetItem(0)
song_item = QtWidgets.QTreeWidgetItem(0)
song_item.addChild(verse_1)
song_item.addChild(verse_2)
self.service_manager.setup_ui(self.service_manager)
self.service_manager.service_manager_list.addTopLevelItem(song_item)
return verse_1, verse_2, song_item
def test_on_expand_selection(self):
"""
Test that the on_expand_selection function successfully expands an item and moves to its first child
"""
# GIVEN a mocked servicemanager list
verse_1, verse_2, song_item = self._setup_service_manager_list()
self.service_manager.service_manager_list.setCurrentItem(song_item)
# Reset expanded function in case it has been called and/or changed in initialisation of the service manager.
self.service_manager.expanded = MagicMock()
# WHEN on_expand_selection is called
self.service_manager.on_expand_selection()
# THEN selection should be expanded
selected_index = self.service_manager.service_manager_list.currentIndex()
above_selected_index = self.service_manager.service_manager_list.indexAbove(selected_index)
self.assertTrue(self.service_manager.service_manager_list.isExpanded(above_selected_index),
'Item should have been expanded')
self.service_manager.expanded.assert_called_once_with(song_item)
def test_on_collapse_selection_with_parent_selected(self):
"""
Test that the on_collapse_selection function successfully collapses an item
"""
# GIVEN a mocked servicemanager list
verse_1, verse_2, song_item = self._setup_service_manager_list()
self.service_manager.service_manager_list.setCurrentItem(song_item)
self.service_manager.service_manager_list.expandItem(song_item)
# Reset collapsed function in case it has been called and/or changed in initialisation of the service manager.
self.service_manager.collapsed = MagicMock()
# WHEN on_expand_selection is called
self.service_manager.on_collapse_selection()
# THEN selection should be expanded
selected_index = self.service_manager.service_manager_list.currentIndex()
self.assertFalse(self.service_manager.service_manager_list.isExpanded(selected_index),
'Item should have been collapsed')
self.assertTrue(self.service_manager.service_manager_list.currentItem() == song_item,
'Top item should have been selected')
self.service_manager.collapsed.assert_called_once_with(song_item)
def test_on_collapse_selection_with_child_selected(self):
"""
Test that the on_collapse_selection function successfully collapses child's parent item
and moves selection to its parent.
"""
# GIVEN a mocked servicemanager list
verse_1, verse_2, song_item = self._setup_service_manager_list()
self.service_manager.service_manager_list.setCurrentItem(verse_2)
self.service_manager.service_manager_list.expandItem(song_item)
# Reset collapsed function in case it has been called and/or changed in initialisation of the service manager.
self.service_manager.collapsed = MagicMock()
# WHEN on_expand_selection is called
self.service_manager.on_collapse_selection()
# THEN selection should be expanded
selected_index = self.service_manager.service_manager_list.currentIndex()
self.assertFalse(self.service_manager.service_manager_list.isExpanded(selected_index),
'Item should have been collapsed')
self.assertTrue(self.service_manager.service_manager_list.currentItem() == song_item,
'Top item should have been selected')
self.service_manager.collapsed.assert_called_once_with(song_item)