openlp/openlp/core/common/__init__.py

419 lines
13 KiB
Python
Raw Normal View History

# -*- coding: utf-8 -*-
2012-12-29 09:35:24 +00:00
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
2015-12-31 22:46:06 +00:00
# Copyright (c) 2008-2016 OpenLP Developers #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
2013-10-13 13:51:13 +00:00
The :mod:`common` module contains most of the components and libraries that make
OpenLP work.
"""
2014-10-06 19:10:03 +00:00
import hashlib
2016-04-16 13:51:42 +00:00
2013-10-13 13:51:13 +00:00
import logging
2016-04-01 16:56:54 +00:00
import os
import re
2013-10-13 17:02:12 +00:00
import sys
2013-12-13 19:44:17 +00:00
import traceback
2014-10-06 19:10:03 +00:00
from ipaddress import IPv4Address, IPv6Address, AddressValueError
2016-04-04 20:14:04 +00:00
from shutil import which
2016-04-16 13:51:42 +00:00
from subprocess import check_output, CalledProcessError, STDOUT
2016-04-05 17:30:20 +00:00
from PyQt5 import QtCore, QtGui
2015-11-07 00:49:40 +00:00
from PyQt5.QtCore import QCryptographicHash as QHash
2013-10-13 20:36:42 +00:00
2014-04-12 20:19:22 +00:00
log = logging.getLogger(__name__ + '.__init__')
2013-02-01 19:58:18 +00:00
2013-10-13 13:51:13 +00:00
2013-12-13 19:44:17 +00:00
FIRST_CAMEL_REGEX = re.compile('(.)([A-Z][a-z]+)')
SECOND_CAMEL_REGEX = re.compile('([a-z0-9])([A-Z])')
2016-04-05 17:30:20 +00:00
CONTROL_CHARS = re.compile(r'[\x00-\x1F\x7F-\x9F]', re.UNICODE)
INVALID_FILE_CHARS = re.compile(r'[\\/:\*\?"<>\|\+\[\]%]', re.UNICODE)
IMAGES_FILTER = None
2013-12-13 19:44:17 +00:00
def trace_error_handler(logger):
"""
Log the calling path of an exception
2014-03-17 19:05:55 +00:00
:param logger: logger to use so traceback is logged to correct class
2013-12-13 19:44:17 +00:00
"""
2014-04-01 17:32:19 +00:00
log_string = "OpenLP Error trace"
2013-12-13 19:44:17 +00:00
for tb in traceback.extract_stack():
log_string += '\n File {file} at line {line} \n\t called {data}'.format(file=tb[0],
line=tb[1],
data=tb[3])
2014-04-01 17:32:19 +00:00
logger.error(log_string)
2013-12-13 19:44:17 +00:00
2013-12-15 16:50:09 +00:00
2013-10-13 13:51:13 +00:00
def check_directory_exists(directory, do_not_log=False):
"""
Check a theme directory exists and if not create it
2014-03-17 19:05:55 +00:00
:param directory: The directory to make sure exists
:param do_not_log: To not log anything. This is need for the start up, when the log isn't ready.
2013-10-13 13:51:13 +00:00
"""
if not do_not_log:
log.debug('check_directory_exists {text}'.format(text=directory))
2013-10-13 13:51:13 +00:00
try:
if not os.path.exists(directory):
os.makedirs(directory)
2015-04-02 12:50:50 +00:00
except IOError as e:
if not do_not_log:
2015-04-02 20:29:43 +00:00
log.exception('failed to check if directory exists or create directory')
2013-10-13 13:51:13 +00:00
2013-10-13 17:02:12 +00:00
def get_frozen_path(frozen_option, non_frozen_option):
"""
Return a path based on the system status.
:param frozen_option:
:param non_frozen_option:
2013-10-13 17:02:12 +00:00
"""
if hasattr(sys, 'frozen') and sys.frozen == 1:
return frozen_option
return non_frozen_option
2013-10-13 20:36:42 +00:00
class ThemeLevel(object):
"""
Provides an enumeration for the level a theme applies to
"""
Global = 1
Service = 2
Song = 3
2015-11-07 00:49:40 +00:00
def translate(context, text, comment=None, qt_translate=QtCore.QCoreApplication.translate):
2013-10-13 20:36:42 +00:00
"""
2015-11-07 00:49:40 +00:00
A special shortcut method to wrap around the Qt5 translation functions. This abstracts the translation procedure so
2013-10-13 20:36:42 +00:00
that we can change it if at a later date if necessary, without having to redo the whole of OpenLP.
2014-03-17 19:05:55 +00:00
:param context: The translation context, used to give each string a context or a namespace.
:param text: The text to put into the translation tables for translation.
:param comment: An identifying string for when the same text is used in different roles within the same context.
:param qt_translate:
2013-10-13 20:36:42 +00:00
"""
2015-11-07 00:49:40 +00:00
return qt_translate(context, text, comment)
2013-10-13 20:36:42 +00:00
class SlideLimits(object):
"""
Provides an enumeration for behaviour of OpenLP at the end limits of each service item when pressing the up/down
arrow keys
"""
End = 1
Wrap = 2
Next = 3
2013-12-13 19:44:17 +00:00
def de_hump(name):
"""
Change any Camel Case string to python string
"""
sub_name = FIRST_CAMEL_REGEX.sub(r'\1_\2', name)
return SECOND_CAMEL_REGEX.sub(r'\1_\2', sub_name).lower()
def is_win():
"""
Returns true if running on a system with a nt kernel e.g. Windows, Wine
:return: True if system is running a nt kernel false otherwise
"""
return os.name.startswith('nt')
def is_macosx():
"""
Returns true if running on a system with a darwin kernel e.g. Mac OS X
:return: True if system is running a darwin kernel false otherwise
"""
return sys.platform.startswith('darwin')
def is_linux():
"""
Returns true if running on a system with a linux kernel e.g. Ubuntu, Debian, etc
:return: True if system is running a linux kernel false otherwise
"""
return sys.platform.startswith('linux')
2014-10-06 19:10:03 +00:00
def verify_ipv4(addr):
"""
Validate an IPv4 address
:param addr: Address to validate
:returns: bool
"""
try:
valid = IPv4Address(addr)
return True
except AddressValueError:
return False
def verify_ipv6(addr):
"""
Validate an IPv6 address
:param addr: Address to validate
:returns: bool
"""
try:
valid = IPv6Address(addr)
return True
except AddressValueError:
return False
def verify_ip_address(addr):
"""
Validate an IP address as either IPv4 or IPv6
:param addr: Address to validate
:returns: bool
"""
return True if verify_ipv4(addr) else verify_ipv6(addr)
def md5_hash(salt=None, data=None):
2014-10-06 19:10:03 +00:00
"""
Returns the hashed output of md5sum on salt,data
using Python3 hashlib
:param salt: Initial salt
:param data: OPTIONAL Data to hash
2014-10-06 19:10:03 +00:00
:returns: str
"""
log.debug('md5_hash(salt="{text}")'.format(text=salt))
if not salt and not data:
return None
2014-10-06 19:10:03 +00:00
hash_obj = hashlib.new('md5')
if salt:
hash_obj.update(salt)
if data:
2016-06-18 02:45:02 +00:00
hash_obj.update(data)
2014-10-06 19:10:03 +00:00
hash_value = hash_obj.hexdigest()
log.debug('md5_hash() returning "{text}"'.format(text=hash_value))
2014-10-06 19:10:03 +00:00
return hash_value
def qmd5_hash(salt=None, data=None):
2014-10-06 19:10:03 +00:00
"""
2014-10-09 20:30:07 +00:00
Returns the hashed output of MD5Sum on salt, data
using PyQt5.QCryptographicHash. Function returns a
QByteArray instead of a text string.
If you need a string instead, call with
result = str(qmd5_hash(salt=..., data=...), encoding='ascii')
2014-10-06 19:10:03 +00:00
:param salt: Initial salt
:param data: OPTIONAL Data to hash
:returns: QByteArray
2014-10-06 19:10:03 +00:00
"""
log.debug('qmd5_hash(salt="{text}"'.format(text=salt))
if salt is None and data is None:
return None
2014-10-06 19:10:03 +00:00
hash_obj = QHash(QHash.Md5)
if salt:
hash_obj.addData(salt)
if data:
2016-06-18 02:45:02 +00:00
hash_obj.addData(data)
2014-10-06 19:10:03 +00:00
hash_value = hash_obj.result().toHex()
log.debug('qmd5_hash() returning "{hash}"'.format(hash=hash_value))
return hash_value
2014-10-06 19:10:03 +00:00
2014-10-31 20:12:06 +00:00
def clean_button_text(button_text):
"""
Clean the & and other characters out of button text
2014-10-31 20:12:06 +00:00
:param button_text: The text to clean
"""
return button_text.replace('&', '').replace('< ', '').replace(' >', '')
2014-10-06 19:10:03 +00:00
2014-10-31 20:12:06 +00:00
2013-12-13 19:44:17 +00:00
from .openlpmixin import OpenLPMixin
2013-12-13 17:44:05 +00:00
from .registry import Registry
2013-12-15 16:50:09 +00:00
from .registrymixin import RegistryMixin
2014-03-12 17:41:52 +00:00
from .registryproperties import RegistryProperties
2013-10-13 20:36:42 +00:00
from .uistrings import UiStrings
from .settings import Settings
2013-10-13 15:52:04 +00:00
from .applocation import AppLocation
2016-03-31 16:34:22 +00:00
from .actions import ActionList
2016-03-31 16:42:42 +00:00
from .languagemanager import LanguageManager
2016-04-01 17:28:40 +00:00
2016-04-16 13:51:42 +00:00
if is_win():
from subprocess import STARTUPINFO, STARTF_USESHOWWINDOW
2016-04-01 17:28:40 +00:00
def add_actions(target, actions):
"""
Adds multiple actions to a menu or toolbar in one command.
:param target: The menu or toolbar to add actions to
:param actions: The actions to be added. An action consisting of the keyword ``None``
will result in a separator being inserted into the target.
"""
for action in actions:
if action is None:
target.addSeparator()
else:
target.addAction(action)
2016-04-04 20:14:04 +00:00
def get_uno_command(connection_type='pipe'):
"""
Returns the UNO command to launch an libreoffice.org instance.
"""
for command in ['libreoffice', 'soffice']:
if which(command):
break
else:
raise FileNotFoundError('Command not found')
OPTIONS = '--nologo --norestore --minimized --nodefault --nofirststartwizard'
if connection_type == 'pipe':
CONNECTION = '"--accept=pipe,name=openlp_pipe;urp;"'
else:
CONNECTION = '"--accept=socket,host=localhost,port=2002;urp;"'
return '{cmd} {opt} {conn}'.format(cmd=command, opt=OPTIONS, conn=CONNECTION)
2016-04-04 20:14:04 +00:00
def get_uno_instance(resolver, connection_type='pipe'):
"""
Returns a running libreoffice.org instance.
:param resolver: The UNO resolver to use to find a running instance.
"""
log.debug('get UNO Desktop Openoffice - resolve')
if connection_type == 'pipe':
return resolver.resolve('uno:pipe,name=openlp_pipe;urp;StarOffice.ComponentContext')
else:
2016-04-04 20:27:33 +00:00
return resolver.resolve('uno:socket,host=localhost,port=2002;urp;StarOffice.ComponentContext')
2016-04-05 16:58:29 +00:00
def get_filesystem_encoding():
"""
Returns the name of the encoding used to convert Unicode filenames into system file names.
"""
encoding = sys.getfilesystemencoding()
if encoding is None:
encoding = sys.getdefaultencoding()
2016-04-05 17:10:51 +00:00
return encoding
def split_filename(path):
"""
Return a list of the parts in a given path.
"""
path = os.path.abspath(path)
if not os.path.isfile(path):
return path, ''
else:
return os.path.split(path)
def delete_file(file_path_name):
"""
Deletes a file from the system.
:param file_path_name: The file, including path, to delete.
"""
if not file_path_name:
return False
try:
if os.path.exists(file_path_name):
os.remove(file_path_name)
return True
except (IOError, OSError):
2016-04-24 11:04:32 +00:00
log.exception("Unable to delete file {text}".format(text=file_path_name))
2016-04-05 17:30:20 +00:00
return False
def get_images_filter():
"""
Returns a filter string for a file dialog containing all the supported image formats.
"""
global IMAGES_FILTER
if not IMAGES_FILTER:
log.debug('Generating images filter.')
formats = list(map(bytes.decode, list(map(bytes, QtGui.QImageReader.supportedImageFormats()))))
visible_formats = '(*.{text})'.format(text='; *.'.join(formats))
actual_formats = '(*.{text})'.format(text=' *.'.join(formats))
IMAGES_FILTER = '{text} {visible} {actual}'.format(text=translate('OpenLP', 'Image Files'),
visible=visible_formats,
actual=actual_formats)
2016-04-05 17:30:20 +00:00
return IMAGES_FILTER
def is_not_image_file(file_name):
"""
Validate that the file is not an image file.
:param file_name: File name to be checked.
"""
if not file_name:
return True
else:
formats = [bytes(fmt).decode().lower() for fmt in QtGui.QImageReader.supportedImageFormats()]
file_part, file_extension = os.path.splitext(str(file_name))
if file_extension[1:].lower() in formats and os.path.exists(file_name):
return False
return True
def clean_filename(filename):
"""
Removes invalid characters from the given ``filename``.
:param filename: The "dirty" file name to clean.
"""
if not isinstance(filename, str):
filename = str(filename, 'utf-8')
2016-04-05 20:07:57 +00:00
return INVALID_FILE_CHARS.sub('_', CONTROL_CHARS.sub('', filename))
2016-04-16 13:51:42 +00:00
2016-04-21 16:26:34 +00:00
def check_binary_exists(program_path):
2016-04-16 13:51:42 +00:00
"""
Function that checks whether a binary exists.
:param program_path:The full path to the binary to check.
:return: program output to be parsed
"""
log.debug('testing program_path: {text}'.format(text=program_path))
2016-04-16 13:51:42 +00:00
try:
# Setup startupinfo options for check_output to avoid console popping up on windows
if is_win():
startupinfo = STARTUPINFO()
startupinfo.dwFlags |= STARTF_USESHOWWINDOW
else:
startupinfo = None
runlog = check_output([program_path, '--help'], stderr=STDOUT, startupinfo=startupinfo)
except CalledProcessError as e:
runlog = e.output
except Exception:
trace_error_handler(log)
runlog = ''
2016-04-24 11:22:04 +00:00
log.debug('check_output returned: {text}'.format(text=runlog))
2016-04-16 13:51:42 +00:00
return runlog