forked from openlp/openlp
HEAD
This commit is contained in:
commit
c2e16cc8e9
Binary file not shown.
Binary file not shown.
|
@ -20,13 +20,18 @@
|
||||||
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
"""
|
||||||
import sys
|
The entrypoint for OpenLP
|
||||||
|
"""
|
||||||
|
import faulthandler
|
||||||
import multiprocessing
|
import multiprocessing
|
||||||
|
import sys
|
||||||
|
|
||||||
from openlp.core.common import is_win, is_macosx
|
from openlp.core.common import is_win, is_macosx
|
||||||
|
from openlp.core.common.applocation import AppLocation
|
||||||
from openlp.core import main
|
from openlp.core import main
|
||||||
|
|
||||||
|
faulthandler.enable(open(str(AppLocation.get_directory(AppLocation.CacheDir) / 'error.log'), 'wb'))
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -26,11 +26,8 @@ The :mod:`core` module provides all core application functions
|
||||||
All the core functions of the OpenLP application including the GUI, settings,
|
All the core functions of the OpenLP application including the GUI, settings,
|
||||||
logging and a plugin framework are contained within the openlp.core module.
|
logging and a plugin framework are contained within the openlp.core module.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import logging
|
import logging
|
||||||
import os
|
|
||||||
import shutil
|
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
@ -40,8 +37,8 @@ from PyQt5 import QtCore, QtGui, QtWidgets
|
||||||
|
|
||||||
from openlp.core.common import Registry, OpenLPMixin, AppLocation, LanguageManager, Settings, UiStrings, \
|
from openlp.core.common import Registry, OpenLPMixin, AppLocation, LanguageManager, Settings, UiStrings, \
|
||||||
check_directory_exists, is_macosx, is_win, translate
|
check_directory_exists, is_macosx, is_win, translate
|
||||||
from openlp.core.common.path import Path
|
from openlp.core.common.path import Path, copytree
|
||||||
from openlp.core.common.versionchecker import VersionThread, get_application_version
|
from openlp.core.version import check_for_update, get_version
|
||||||
from openlp.core.lib import ScreenList
|
from openlp.core.lib import ScreenList
|
||||||
from openlp.core.resources import qInitResources
|
from openlp.core.resources import qInitResources
|
||||||
from openlp.core.ui import SplashScreen
|
from openlp.core.ui import SplashScreen
|
||||||
|
@ -160,8 +157,8 @@ class OpenLP(OpenLPMixin, QtWidgets.QApplication):
|
||||||
self.processEvents()
|
self.processEvents()
|
||||||
if not has_run_wizard:
|
if not has_run_wizard:
|
||||||
self.main_window.first_time()
|
self.main_window.first_time()
|
||||||
version = VersionThread(self.main_window)
|
if Settings().value('core/update check'):
|
||||||
version.start()
|
check_for_update(self.main_window)
|
||||||
self.main_window.is_display_blank()
|
self.main_window.is_display_blank()
|
||||||
self.main_window.app_startup()
|
self.main_window.app_startup()
|
||||||
return self.exec()
|
return self.exec()
|
||||||
|
@ -186,25 +183,20 @@ class OpenLP(OpenLPMixin, QtWidgets.QApplication):
|
||||||
"""
|
"""
|
||||||
Check if the data folder path exists.
|
Check if the data folder path exists.
|
||||||
"""
|
"""
|
||||||
data_folder_path = str(AppLocation.get_data_path())
|
data_folder_path = AppLocation.get_data_path()
|
||||||
if not os.path.exists(data_folder_path):
|
if not data_folder_path.exists():
|
||||||
log.critical('Database was not found in: ' + data_folder_path)
|
log.critical('Database was not found in: %s', data_folder_path)
|
||||||
status = QtWidgets.QMessageBox.critical(None, translate('OpenLP', 'Data Directory Error'),
|
status = QtWidgets.QMessageBox.critical(
|
||||||
translate('OpenLP', 'OpenLP data folder was not found in:\n\n{path}'
|
None, translate('OpenLP', 'Data Directory Error'),
|
||||||
'\n\nThe location of the data folder was '
|
translate('OpenLP', 'OpenLP data folder was not found in:\n\n{path}\n\nThe location of the data folder '
|
||||||
'previously changed from the OpenLP\'s '
|
'was previously changed from the OpenLP\'s default location. If the data was '
|
||||||
'default location. If the data was stored on '
|
'stored on removable device, that device needs to be made available.\n\nYou may '
|
||||||
'removable device, that device needs to be '
|
'reset the data location back to the default location, or you can try to make the '
|
||||||
'made available.\n\nYou may reset the data '
|
'current location available.\n\nDo you want to reset to the default data location? '
|
||||||
'location back to the default location, '
|
'If not, OpenLP will be closed so you can try to fix the the problem.')
|
||||||
'or you can try to make the current location '
|
.format(path=data_folder_path),
|
||||||
'available.\n\nDo you want to reset to the '
|
QtWidgets.QMessageBox.StandardButtons(QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No),
|
||||||
'default data location? If not, OpenLP will be '
|
QtWidgets.QMessageBox.No)
|
||||||
'closed so you can try to fix the the problem.')
|
|
||||||
.format(path=data_folder_path),
|
|
||||||
QtWidgets.QMessageBox.StandardButtons(QtWidgets.QMessageBox.Yes |
|
|
||||||
QtWidgets.QMessageBox.No),
|
|
||||||
QtWidgets.QMessageBox.No)
|
|
||||||
if status == QtWidgets.QMessageBox.No:
|
if status == QtWidgets.QMessageBox.No:
|
||||||
# If answer was "No", return "True", it will shutdown OpenLP in def main
|
# If answer was "No", return "True", it will shutdown OpenLP in def main
|
||||||
log.info('User requested termination')
|
log.info('User requested termination')
|
||||||
|
@ -245,7 +237,7 @@ class OpenLP(OpenLPMixin, QtWidgets.QApplication):
|
||||||
:param can_show_splash: Should OpenLP show the splash screen
|
:param can_show_splash: Should OpenLP show the splash screen
|
||||||
"""
|
"""
|
||||||
data_version = Settings().value('core/application version')
|
data_version = Settings().value('core/application version')
|
||||||
openlp_version = get_application_version()['version']
|
openlp_version = get_version()['version']
|
||||||
# New installation, no need to create backup
|
# New installation, no need to create backup
|
||||||
if not has_run_wizard:
|
if not has_run_wizard:
|
||||||
Settings().setValue('core/application version', openlp_version)
|
Settings().setValue('core/application version', openlp_version)
|
||||||
|
@ -258,11 +250,11 @@ class OpenLP(OpenLPMixin, QtWidgets.QApplication):
|
||||||
'a backup of the old data folder?'),
|
'a backup of the old data folder?'),
|
||||||
defaultButton=QtWidgets.QMessageBox.Yes) == QtWidgets.QMessageBox.Yes:
|
defaultButton=QtWidgets.QMessageBox.Yes) == QtWidgets.QMessageBox.Yes:
|
||||||
# Create copy of data folder
|
# Create copy of data folder
|
||||||
data_folder_path = str(AppLocation.get_data_path())
|
data_folder_path = AppLocation.get_data_path()
|
||||||
timestamp = time.strftime("%Y%m%d-%H%M%S")
|
timestamp = time.strftime("%Y%m%d-%H%M%S")
|
||||||
data_folder_backup_path = data_folder_path + '-' + timestamp
|
data_folder_backup_path = data_folder_path.with_name(data_folder_path.name + '-' + timestamp)
|
||||||
try:
|
try:
|
||||||
shutil.copytree(data_folder_path, data_folder_backup_path)
|
copytree(data_folder_path, data_folder_backup_path)
|
||||||
except OSError:
|
except OSError:
|
||||||
QtWidgets.QMessageBox.warning(None, translate('OpenLP', 'Backup'),
|
QtWidgets.QMessageBox.warning(None, translate('OpenLP', 'Backup'),
|
||||||
translate('OpenLP', 'Backup of the data folder failed!'))
|
translate('OpenLP', 'Backup of the data folder failed!'))
|
||||||
|
@ -420,7 +412,7 @@ def main(args=None):
|
||||||
Registry.create()
|
Registry.create()
|
||||||
Registry().register('application', application)
|
Registry().register('application', application)
|
||||||
Registry().set_flag('no_web_server', args.no_web_server)
|
Registry().set_flag('no_web_server', args.no_web_server)
|
||||||
application.setApplicationVersion(get_application_version()['version'])
|
application.setApplicationVersion(get_version()['version'])
|
||||||
# Check if an instance of OpenLP is already running. Quit if there is a running instance and the user only wants one
|
# Check if an instance of OpenLP is already running. Quit if there is a running instance and the user only wants one
|
||||||
if application.is_already_running():
|
if application.is_already_running():
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
|
|
@ -52,7 +52,7 @@ class Poller(RegistryProperties):
|
||||||
'isSecure': Settings().value('api/authentication enabled'),
|
'isSecure': Settings().value('api/authentication enabled'),
|
||||||
'isAuthorised': False,
|
'isAuthorised': False,
|
||||||
'chordNotation': Settings().value('songs/chord notation'),
|
'chordNotation': Settings().value('songs/chord notation'),
|
||||||
'isStagedActive': self.is_stage_active(),
|
'isStageActive': self.is_stage_active(),
|
||||||
'isLiveActive': self.is_live_active(),
|
'isLiveActive': self.is_live_active(),
|
||||||
'isChordsActive': self.is_chords_active()
|
'isChordsActive': self.is_chords_active()
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,6 @@ import sys
|
||||||
from openlp.core.common import Settings, is_win, is_macosx
|
from openlp.core.common import Settings, is_win, is_macosx
|
||||||
from openlp.core.common.path import Path
|
from openlp.core.common.path import Path
|
||||||
|
|
||||||
|
|
||||||
if not is_win() and not is_macosx():
|
if not is_win() and not is_macosx():
|
||||||
try:
|
try:
|
||||||
from xdg import BaseDirectory
|
from xdg import BaseDirectory
|
||||||
|
|
|
@ -24,18 +24,12 @@ The :mod:`openlp.core.utils` module provides the utility libraries for OpenLP.
|
||||||
"""
|
"""
|
||||||
import hashlib
|
import hashlib
|
||||||
import logging
|
import logging
|
||||||
import os
|
|
||||||
import platform
|
|
||||||
import socket
|
|
||||||
import sys
|
import sys
|
||||||
import subprocess
|
|
||||||
import time
|
import time
|
||||||
import urllib.error
|
|
||||||
import urllib.parse
|
|
||||||
import urllib.request
|
|
||||||
from http.client import HTTPException
|
|
||||||
from random import randint
|
from random import randint
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
from openlp.core.common import Registry, trace_error_handler
|
from openlp.core.common import Registry, trace_error_handler
|
||||||
|
|
||||||
log = logging.getLogger(__name__ + '.__init__')
|
log = logging.getLogger(__name__ + '.__init__')
|
||||||
|
@ -69,33 +63,6 @@ CONNECTION_TIMEOUT = 30
|
||||||
CONNECTION_RETRIES = 2
|
CONNECTION_RETRIES = 2
|
||||||
|
|
||||||
|
|
||||||
class HTTPRedirectHandlerFixed(urllib.request.HTTPRedirectHandler):
|
|
||||||
"""
|
|
||||||
Special HTTPRedirectHandler used to work around http://bugs.python.org/issue22248
|
|
||||||
(Redirecting to urls with special chars)
|
|
||||||
"""
|
|
||||||
def redirect_request(self, req, fp, code, msg, headers, new_url):
|
|
||||||
#
|
|
||||||
"""
|
|
||||||
Test if the new_url can be decoded to ascii
|
|
||||||
|
|
||||||
:param req:
|
|
||||||
:param fp:
|
|
||||||
:param code:
|
|
||||||
:param msg:
|
|
||||||
:param headers:
|
|
||||||
:param new_url:
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
new_url.encode('latin1').decode('ascii')
|
|
||||||
fixed_url = new_url
|
|
||||||
except Exception:
|
|
||||||
# The url could not be decoded to ascii, so we do some url encoding
|
|
||||||
fixed_url = urllib.parse.quote(new_url.encode('latin1').decode('utf-8', 'replace'), safe='/:')
|
|
||||||
return super(HTTPRedirectHandlerFixed, self).redirect_request(req, fp, code, msg, headers, fixed_url)
|
|
||||||
|
|
||||||
|
|
||||||
def get_user_agent():
|
def get_user_agent():
|
||||||
"""
|
"""
|
||||||
Return a user agent customised for the platform the user is on.
|
Return a user agent customised for the platform the user is on.
|
||||||
|
@ -107,7 +74,7 @@ def get_user_agent():
|
||||||
return browser_list[random_index]
|
return browser_list[random_index]
|
||||||
|
|
||||||
|
|
||||||
def get_web_page(url, header=None, update_openlp=False):
|
def get_web_page(url, headers=None, update_openlp=False, proxies=None):
|
||||||
"""
|
"""
|
||||||
Attempts to download the webpage at url and returns that page or None.
|
Attempts to download the webpage at url and returns that page or None.
|
||||||
|
|
||||||
|
@ -116,71 +83,36 @@ def get_web_page(url, header=None, update_openlp=False):
|
||||||
:param update_openlp: Tells OpenLP to update itself if the page is successfully downloaded.
|
:param update_openlp: Tells OpenLP to update itself if the page is successfully downloaded.
|
||||||
Defaults to False.
|
Defaults to False.
|
||||||
"""
|
"""
|
||||||
# TODO: Add proxy usage. Get proxy info from OpenLP settings, add to a
|
|
||||||
# proxy_handler, build into an opener and install the opener into urllib2.
|
|
||||||
# http://docs.python.org/library/urllib2.html
|
|
||||||
if not url:
|
if not url:
|
||||||
return None
|
return None
|
||||||
# This is needed to work around http://bugs.python.org/issue22248 and https://bugs.launchpad.net/openlp/+bug/1251437
|
if not headers:
|
||||||
opener = urllib.request.build_opener(HTTPRedirectHandlerFixed())
|
headers = {}
|
||||||
urllib.request.install_opener(opener)
|
if 'user-agent' not in [key.lower() for key in headers.keys()]:
|
||||||
req = urllib.request.Request(url)
|
headers['User-Agent'] = get_user_agent()
|
||||||
if not header or header[0].lower() != 'user-agent':
|
|
||||||
user_agent = get_user_agent()
|
|
||||||
req.add_header('User-Agent', user_agent)
|
|
||||||
if header:
|
|
||||||
req.add_header(header[0], header[1])
|
|
||||||
log.debug('Downloading URL = %s' % url)
|
log.debug('Downloading URL = %s' % url)
|
||||||
retries = 0
|
retries = 0
|
||||||
while retries <= CONNECTION_RETRIES:
|
while retries < CONNECTION_RETRIES:
|
||||||
retries += 1
|
|
||||||
time.sleep(0.1)
|
|
||||||
try:
|
try:
|
||||||
page = urllib.request.urlopen(req, timeout=CONNECTION_TIMEOUT)
|
response = requests.get(url, headers=headers, proxies=proxies, timeout=float(CONNECTION_TIMEOUT))
|
||||||
log.debug('Downloaded page {text}'.format(text=page.geturl()))
|
log.debug('Downloaded page {url}'.format(url=response.url))
|
||||||
break
|
break
|
||||||
except urllib.error.URLError as err:
|
except IOError:
|
||||||
log.exception('URLError on {text}'.format(text=url))
|
# For now, catch IOError. All requests errors inherit from IOError
|
||||||
log.exception('URLError: {text}'.format(text=err.reason))
|
log.exception('Unable to connect to {url}'.format(url=url))
|
||||||
page = None
|
response = None
|
||||||
if retries > CONNECTION_RETRIES:
|
if retries >= CONNECTION_RETRIES:
|
||||||
raise
|
raise ConnectionError('Unable to connect to {url}, see log for details'.format(url=url))
|
||||||
except socket.timeout:
|
retries += 1
|
||||||
log.exception('Socket timeout: {text}'.format(text=url))
|
|
||||||
page = None
|
|
||||||
if retries > CONNECTION_RETRIES:
|
|
||||||
raise
|
|
||||||
except socket.gaierror:
|
|
||||||
log.exception('Socket gaierror: {text}'.format(text=url))
|
|
||||||
page = None
|
|
||||||
if retries > CONNECTION_RETRIES:
|
|
||||||
raise
|
|
||||||
except ConnectionRefusedError:
|
|
||||||
log.exception('ConnectionRefused: {text}'.format(text=url))
|
|
||||||
page = None
|
|
||||||
if retries > CONNECTION_RETRIES:
|
|
||||||
raise
|
|
||||||
break
|
|
||||||
except ConnectionError:
|
|
||||||
log.exception('Connection error: {text}'.format(text=url))
|
|
||||||
page = None
|
|
||||||
if retries > CONNECTION_RETRIES:
|
|
||||||
raise
|
|
||||||
except HTTPException:
|
|
||||||
log.exception('HTTPException error: {text}'.format(text=url))
|
|
||||||
page = None
|
|
||||||
if retries > CONNECTION_RETRIES:
|
|
||||||
raise
|
|
||||||
except:
|
except:
|
||||||
# Don't know what's happening, so reraise the original
|
# Don't know what's happening, so reraise the original
|
||||||
|
log.exception('Unknown error when trying to connect to {url}'.format(url=url))
|
||||||
raise
|
raise
|
||||||
if update_openlp:
|
if update_openlp:
|
||||||
Registry().get('application').process_events()
|
Registry().get('application').process_events()
|
||||||
if not page:
|
if not response or not response.text:
|
||||||
log.exception('{text} could not be downloaded'.format(text=url))
|
log.error('{url} could not be downloaded'.format(url=url))
|
||||||
return None
|
return None
|
||||||
log.debug(page)
|
return response.text
|
||||||
return page
|
|
||||||
|
|
||||||
|
|
||||||
def get_url_file_size(url):
|
def get_url_file_size(url):
|
||||||
|
@ -192,81 +124,67 @@ def get_url_file_size(url):
|
||||||
retries = 0
|
retries = 0
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
site = urllib.request.urlopen(url, timeout=CONNECTION_TIMEOUT)
|
response = requests.head(url, timeout=float(CONNECTION_TIMEOUT), allow_redirects=True)
|
||||||
meta = site.info()
|
return int(response.headers['Content-Length'])
|
||||||
return int(meta.get("Content-Length"))
|
except IOError:
|
||||||
except urllib.error.URLError:
|
|
||||||
if retries > CONNECTION_RETRIES:
|
if retries > CONNECTION_RETRIES:
|
||||||
raise
|
raise ConnectionError('Unable to download {url}'.format(url=url))
|
||||||
else:
|
else:
|
||||||
retries += 1
|
retries += 1
|
||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|
||||||
def url_get_file(callback, url, f_path, sha256=None):
|
def url_get_file(callback, url, file_path, sha256=None):
|
||||||
""""
|
""""
|
||||||
Download a file given a URL. The file is retrieved in chunks, giving the ability to cancel the download at any
|
Download a file given a URL. The file is retrieved in chunks, giving the ability to cancel the download at any
|
||||||
point. Returns False on download error.
|
point. Returns False on download error.
|
||||||
|
|
||||||
:param callback: the class which needs to be updated
|
:param callback: the class which needs to be updated
|
||||||
:param url: URL to download
|
:param url: URL to download
|
||||||
:param f_path: Destination file
|
:param file_path: Destination file
|
||||||
:param sha256: The check sum value to be checked against the download value
|
:param sha256: The check sum value to be checked against the download value
|
||||||
"""
|
"""
|
||||||
block_count = 0
|
block_count = 0
|
||||||
block_size = 4096
|
block_size = 4096
|
||||||
retries = 0
|
retries = 0
|
||||||
log.debug("url_get_file: " + url)
|
log.debug('url_get_file: %s', url)
|
||||||
while True:
|
while retries < CONNECTION_RETRIES:
|
||||||
try:
|
try:
|
||||||
filename = open(f_path, "wb")
|
with file_path.open('wb') as saved_file:
|
||||||
url_file = urllib.request.urlopen(url, timeout=CONNECTION_TIMEOUT)
|
response = requests.get(url, timeout=float(CONNECTION_TIMEOUT), stream=True)
|
||||||
if sha256:
|
|
||||||
hasher = hashlib.sha256()
|
|
||||||
# Download until finished or canceled.
|
|
||||||
while not callback.was_cancelled:
|
|
||||||
data = url_file.read(block_size)
|
|
||||||
if not data:
|
|
||||||
break
|
|
||||||
filename.write(data)
|
|
||||||
if sha256:
|
if sha256:
|
||||||
hasher.update(data)
|
hasher = hashlib.sha256()
|
||||||
block_count += 1
|
# Download until finished or canceled.
|
||||||
callback._download_progress(block_count, block_size)
|
for chunk in response.iter_content(chunk_size=block_size):
|
||||||
filename.close()
|
if callback.was_cancelled:
|
||||||
|
break
|
||||||
|
saved_file.write(chunk)
|
||||||
|
if sha256:
|
||||||
|
hasher.update(chunk)
|
||||||
|
block_count += 1
|
||||||
|
callback._download_progress(block_count, block_size)
|
||||||
|
response.close()
|
||||||
if sha256 and hasher.hexdigest() != sha256:
|
if sha256 and hasher.hexdigest() != sha256:
|
||||||
log.error('sha256 sums did not match for file: {file}'.format(file=f_path))
|
log.error('sha256 sums did not match for file %s, got %s, expected %s', file_path, hasher.hexdigest(),
|
||||||
os.remove(f_path)
|
sha256)
|
||||||
|
if file_path.exists():
|
||||||
|
file_path.unlink()
|
||||||
return False
|
return False
|
||||||
except (urllib.error.URLError, socket.timeout) as err:
|
break
|
||||||
|
except IOError:
|
||||||
trace_error_handler(log)
|
trace_error_handler(log)
|
||||||
filename.close()
|
|
||||||
os.remove(f_path)
|
|
||||||
if retries > CONNECTION_RETRIES:
|
if retries > CONNECTION_RETRIES:
|
||||||
|
if file_path.exists():
|
||||||
|
file_path.unlink()
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
retries += 1
|
retries += 1
|
||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
continue
|
continue
|
||||||
break
|
if callback.was_cancelled and file_path.exists():
|
||||||
# Delete file if cancelled, it may be a partial file.
|
file_path.unlink()
|
||||||
if callback.was_cancelled:
|
|
||||||
os.remove(f_path)
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def ping(host):
|
|
||||||
"""
|
|
||||||
Returns True if host responds to a ping request
|
|
||||||
"""
|
|
||||||
# Ping parameters as function of OS
|
|
||||||
ping_str = "-n 1" if platform.system().lower() == "windows" else "-c 1"
|
|
||||||
args = "ping " + " " + ping_str + " " + host
|
|
||||||
need_sh = False if platform.system().lower() == "windows" else True
|
|
||||||
|
|
||||||
# Ping
|
|
||||||
return subprocess.call(args, shell=need_sh) == 0
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['get_web_page']
|
__all__ = ['get_web_page']
|
||||||
|
|
|
@ -141,7 +141,7 @@ class LanguageManager(object):
|
||||||
if reg_ex.exactMatch(qmf):
|
if reg_ex.exactMatch(qmf):
|
||||||
name = '{regex}'.format(regex=reg_ex.cap(1))
|
name = '{regex}'.format(regex=reg_ex.cap(1))
|
||||||
LanguageManager.__qm_list__[
|
LanguageManager.__qm_list__[
|
||||||
'{count:>2i} {name}'.format(count=counter + 1, name=LanguageManager.language_name(qmf))] = name
|
'{count:>2d} {name}'.format(count=counter + 1, name=LanguageManager.language_name(qmf))] = name
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_qm_list():
|
def get_qm_list():
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
import shutil
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
|
|
||||||
from openlp.core.common import is_win
|
from openlp.core.common import is_win
|
||||||
|
@ -29,6 +30,121 @@ else:
|
||||||
from pathlib import PosixPath as PathVariant
|
from pathlib import PosixPath as PathVariant
|
||||||
|
|
||||||
|
|
||||||
|
def replace_params(args, kwargs, params):
|
||||||
|
"""
|
||||||
|
Apply a transformation function to the specified args or kwargs
|
||||||
|
|
||||||
|
:param tuple args: Positional arguments
|
||||||
|
:param dict kwargs: Key Word arguments
|
||||||
|
:param params: A tuple of tuples with the position and the key word to replace.
|
||||||
|
:return: The modified positional and keyword arguments
|
||||||
|
:rtype: tuple[tuple, dict]
|
||||||
|
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
Take a method with the following signature, and assume we which to apply the str function to arg2:
|
||||||
|
def method(arg1=None, arg2=None, arg3=None)
|
||||||
|
|
||||||
|
As arg2 can be specified postitionally as the second argument (1 with a zero index) or as a keyword, the we
|
||||||
|
would call this function as follows:
|
||||||
|
|
||||||
|
replace_params(args, kwargs, ((1, 'arg2', str),))
|
||||||
|
"""
|
||||||
|
args = list(args)
|
||||||
|
for position, key_word, transform in params:
|
||||||
|
if len(args) > position:
|
||||||
|
args[position] = transform(args[position])
|
||||||
|
elif key_word in kwargs:
|
||||||
|
kwargs[key_word] = transform(kwargs[key_word])
|
||||||
|
return tuple(args), kwargs
|
||||||
|
|
||||||
|
|
||||||
|
def copy(*args, **kwargs):
|
||||||
|
"""
|
||||||
|
Wraps :func:`shutil.copy` so that we can accept Path objects.
|
||||||
|
|
||||||
|
:param src openlp.core.common.path.Path: Takes a Path object which is then converted to a str object
|
||||||
|
:param dst openlp.core.common.path.Path: Takes a Path object which is then converted to a str object
|
||||||
|
:return: Converts the str object received from :func:`shutil.copy` to a Path or NoneType object
|
||||||
|
:rtype: openlp.core.common.path.Path | None
|
||||||
|
|
||||||
|
See the following link for more information on the other parameters:
|
||||||
|
https://docs.python.org/3/library/shutil.html#shutil.copy
|
||||||
|
"""
|
||||||
|
|
||||||
|
args, kwargs = replace_params(args, kwargs, ((0, 'src', path_to_str), (1, 'dst', path_to_str)))
|
||||||
|
|
||||||
|
return str_to_path(shutil.copy(*args, **kwargs))
|
||||||
|
|
||||||
|
|
||||||
|
def copyfile(*args, **kwargs):
|
||||||
|
"""
|
||||||
|
Wraps :func:`shutil.copyfile` so that we can accept Path objects.
|
||||||
|
|
||||||
|
:param openlp.core.common.path.Path src: Takes a Path object which is then converted to a str object
|
||||||
|
:param openlp.core.common.path.Path dst: Takes a Path object which is then converted to a str object
|
||||||
|
:return: Converts the str object received from :func:`shutil.copyfile` to a Path or NoneType object
|
||||||
|
:rtype: openlp.core.common.path.Path | None
|
||||||
|
|
||||||
|
See the following link for more information on the other parameters:
|
||||||
|
https://docs.python.org/3/library/shutil.html#shutil.copyfile
|
||||||
|
"""
|
||||||
|
|
||||||
|
args, kwargs = replace_params(args, kwargs, ((0, 'src', path_to_str), (1, 'dst', path_to_str)))
|
||||||
|
|
||||||
|
return str_to_path(shutil.copyfile(*args, **kwargs))
|
||||||
|
|
||||||
|
|
||||||
|
def copytree(*args, **kwargs):
|
||||||
|
"""
|
||||||
|
Wraps :func:shutil.copytree` so that we can accept Path objects.
|
||||||
|
|
||||||
|
:param openlp.core.common.path.Path src : Takes a Path object which is then converted to a str object
|
||||||
|
:param openlp.core.common.path.Path dst: Takes a Path object which is then converted to a str object
|
||||||
|
:return: Converts the str object received from :func:`shutil.copytree` to a Path or NoneType object
|
||||||
|
:rtype: openlp.core.common.path.Path | None
|
||||||
|
|
||||||
|
See the following link for more information on the other parameters:
|
||||||
|
https://docs.python.org/3/library/shutil.html#shutil.copytree
|
||||||
|
"""
|
||||||
|
|
||||||
|
args, kwargs = replace_params(args, kwargs, ((0, 'src', path_to_str), (1, 'dst', path_to_str)))
|
||||||
|
|
||||||
|
return str_to_path(shutil.copytree(*args, **kwargs))
|
||||||
|
|
||||||
|
|
||||||
|
def rmtree(*args, **kwargs):
|
||||||
|
"""
|
||||||
|
Wraps :func:shutil.rmtree` so that we can accept Path objects.
|
||||||
|
|
||||||
|
:param openlp.core.common.path.Path path: Takes a Path object which is then converted to a str object
|
||||||
|
:return: Passes the return from :func:`shutil.rmtree` back
|
||||||
|
:rtype: None
|
||||||
|
|
||||||
|
See the following link for more information on the other parameters:
|
||||||
|
https://docs.python.org/3/library/shutil.html#shutil.rmtree
|
||||||
|
"""
|
||||||
|
|
||||||
|
args, kwargs = replace_params(args, kwargs, ((0, 'path', path_to_str),))
|
||||||
|
|
||||||
|
return shutil.rmtree(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def which(*args, **kwargs):
|
||||||
|
"""
|
||||||
|
Wraps :func:shutil.which` so that it return a Path objects.
|
||||||
|
|
||||||
|
:rtype: openlp.core.common.Path
|
||||||
|
|
||||||
|
See the following link for more information on the other parameters:
|
||||||
|
https://docs.python.org/3/library/shutil.html#shutil.which
|
||||||
|
"""
|
||||||
|
file_name = shutil.which(*args, **kwargs)
|
||||||
|
if file_name:
|
||||||
|
return str_to_path(file_name)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def path_to_str(path=None):
|
def path_to_str(path=None):
|
||||||
"""
|
"""
|
||||||
A utility function to convert a Path object or NoneType to a string equivalent.
|
A utility function to convert a Path object or NoneType to a string equivalent.
|
||||||
|
|
|
@ -143,7 +143,7 @@ class Registry(object):
|
||||||
log.exception('Exception for function {function}'.format(function=function))
|
log.exception('Exception for function {function}'.format(function=function))
|
||||||
else:
|
else:
|
||||||
trace_error_handler(log)
|
trace_error_handler(log)
|
||||||
log.error("Event {event} called but not registered".format(event=event))
|
log.exception('Event {event} called but not registered'.format(event=event))
|
||||||
return results
|
return results
|
||||||
|
|
||||||
def get_flag(self, key):
|
def get_flag(self, key):
|
||||||
|
|
|
@ -88,9 +88,6 @@ class UiStrings(object):
|
||||||
self.Error = translate('OpenLP.Ui', 'Error')
|
self.Error = translate('OpenLP.Ui', 'Error')
|
||||||
self.Export = translate('OpenLP.Ui', 'Export')
|
self.Export = translate('OpenLP.Ui', 'Export')
|
||||||
self.File = translate('OpenLP.Ui', 'File')
|
self.File = translate('OpenLP.Ui', 'File')
|
||||||
self.FileNotFound = translate('OpenLP.Ui', 'File Not Found')
|
|
||||||
self.FileNotFoundMessage = translate('OpenLP.Ui',
|
|
||||||
'File {name} not found.\nPlease try selecting it individually.')
|
|
||||||
self.FontSizePtUnit = translate('OpenLP.Ui', 'pt', 'Abbreviated font pointsize unit')
|
self.FontSizePtUnit = translate('OpenLP.Ui', 'pt', 'Abbreviated font pointsize unit')
|
||||||
self.Help = translate('OpenLP.Ui', 'Help')
|
self.Help = translate('OpenLP.Ui', 'Help')
|
||||||
self.Hours = translate('OpenLP.Ui', 'h', 'The abbreviated unit for hours')
|
self.Hours = translate('OpenLP.Ui', 'h', 'The abbreviated unit for hours')
|
||||||
|
|
|
@ -32,6 +32,7 @@ import math
|
||||||
from PyQt5 import QtCore, QtGui, Qt, QtWidgets
|
from PyQt5 import QtCore, QtGui, Qt, QtWidgets
|
||||||
|
|
||||||
from openlp.core.common import translate
|
from openlp.core.common import translate
|
||||||
|
from openlp.core.common.path import Path
|
||||||
|
|
||||||
log = logging.getLogger(__name__ + '.__init__')
|
log = logging.getLogger(__name__ + '.__init__')
|
||||||
|
|
||||||
|
@ -125,10 +126,11 @@ def build_icon(icon):
|
||||||
Build a QIcon instance from an existing QIcon, a resource location, or a physical file location. If the icon is a
|
Build a QIcon instance from an existing QIcon, a resource location, or a physical file location. If the icon is a
|
||||||
QIcon instance, that icon is simply returned. If not, it builds a QIcon instance from the resource or file name.
|
QIcon instance, that icon is simply returned. If not, it builds a QIcon instance from the resource or file name.
|
||||||
|
|
||||||
:param icon:
|
:param QtGui.QIcon | Path | QtGui.QIcon | str icon:
|
||||||
The icon to build. This can be a QIcon, a resource string in the form ``:/resource/file.png``, or a file
|
The icon to build. This can be a QIcon, a resource string in the form ``:/resource/file.png``, or a file path
|
||||||
location like ``/path/to/file.png``. However, the **recommended** way is to specify a resource string.
|
location like ``Path(/path/to/file.png)``. However, the **recommended** way is to specify a resource string.
|
||||||
:return: The build icon.
|
:return: The build icon.
|
||||||
|
:rtype: QtGui.QIcon
|
||||||
"""
|
"""
|
||||||
if isinstance(icon, QtGui.QIcon):
|
if isinstance(icon, QtGui.QIcon):
|
||||||
return icon
|
return icon
|
||||||
|
@ -136,6 +138,8 @@ def build_icon(icon):
|
||||||
button_icon = QtGui.QIcon()
|
button_icon = QtGui.QIcon()
|
||||||
if isinstance(icon, str):
|
if isinstance(icon, str):
|
||||||
pix_map = QtGui.QPixmap(icon)
|
pix_map = QtGui.QPixmap(icon)
|
||||||
|
elif isinstance(icon, Path):
|
||||||
|
pix_map = QtGui.QPixmap(str(icon))
|
||||||
elif isinstance(icon, QtGui.QImage):
|
elif isinstance(icon, QtGui.QImage):
|
||||||
pix_map = QtGui.QPixmap.fromImage(icon)
|
pix_map = QtGui.QPixmap.fromImage(icon)
|
||||||
if pix_map:
|
if pix_map:
|
||||||
|
@ -217,14 +221,15 @@ def validate_thumb(file_path, thumb_path):
|
||||||
Validates whether an file's thumb still exists and if is up to date. **Note**, you must **not** call this function,
|
Validates whether an file's thumb still exists and if is up to date. **Note**, you must **not** call this function,
|
||||||
before checking the existence of the file.
|
before checking the existence of the file.
|
||||||
|
|
||||||
:param file_path: The path to the file. The file **must** exist!
|
:param openlp.core.common.path.Path file_path: The path to the file. The file **must** exist!
|
||||||
:param thumb_path: The path to the thumb.
|
:param openlp.core.common.path.Path thumb_path: The path to the thumb.
|
||||||
:return: True, False if the image has changed since the thumb was created.
|
:return: Has the image changed since the thumb was created?
|
||||||
|
:rtype: bool
|
||||||
"""
|
"""
|
||||||
if not os.path.exists(thumb_path):
|
if not thumb_path.exists():
|
||||||
return False
|
return False
|
||||||
image_date = os.stat(file_path).st_mtime
|
image_date = file_path.stat().st_mtime
|
||||||
thumb_date = os.stat(thumb_path).st_mtime
|
thumb_date = thumb_path.stat().st_mtime
|
||||||
return image_date <= thumb_date
|
return image_date <= thumb_date
|
||||||
|
|
||||||
|
|
||||||
|
@ -606,35 +611,6 @@ def create_separated_list(string_list):
|
||||||
return list_to_string
|
return list_to_string
|
||||||
|
|
||||||
|
|
||||||
def replace_params(args, kwargs, params):
|
|
||||||
"""
|
|
||||||
Apply a transformation function to the specified args or kwargs
|
|
||||||
|
|
||||||
:param tuple args: Positional arguments
|
|
||||||
:param dict kwargs: Key Word arguments
|
|
||||||
:param params: A tuple of tuples with the position and the key word to replace.
|
|
||||||
:return: The modified positional and keyword arguments
|
|
||||||
:rtype: tuple[tuple, dict]
|
|
||||||
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
Take a method with the following signature, and assume we which to apply the str function to arg2:
|
|
||||||
def method(arg1=None, arg2=None, arg3=None)
|
|
||||||
|
|
||||||
As arg2 can be specified postitionally as the second argument (1 with a zero index) or as a keyword, the we
|
|
||||||
would call this function as follows:
|
|
||||||
|
|
||||||
replace_params(args, kwargs, ((1, 'arg2', str),))
|
|
||||||
"""
|
|
||||||
args = list(args)
|
|
||||||
for position, key_word, transform in params:
|
|
||||||
if len(args) > position:
|
|
||||||
args[position] = transform(args[position])
|
|
||||||
elif key_word in kwargs:
|
|
||||||
kwargs[key_word] = transform(kwargs[key_word])
|
|
||||||
return tuple(args), kwargs
|
|
||||||
|
|
||||||
|
|
||||||
from .exceptions import ValidationError
|
from .exceptions import ValidationError
|
||||||
from .screen import ScreenList
|
from .screen import ScreenList
|
||||||
from .formattingtags import FormattingTags
|
from .formattingtags import FormattingTags
|
||||||
|
|
|
@ -23,12 +23,13 @@
|
||||||
"""
|
"""
|
||||||
The :mod:`db` module provides the core database functionality for OpenLP
|
The :mod:`db` module provides the core database functionality for OpenLP
|
||||||
"""
|
"""
|
||||||
|
import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
from copy import copy
|
from copy import copy
|
||||||
from urllib.parse import quote_plus as urlquote
|
from urllib.parse import quote_plus as urlquote
|
||||||
|
|
||||||
from sqlalchemy import Table, MetaData, Column, types, create_engine
|
from sqlalchemy import Table, MetaData, Column, types, create_engine, UnicodeText
|
||||||
from sqlalchemy.engine.url import make_url
|
from sqlalchemy.engine.url import make_url
|
||||||
from sqlalchemy.exc import SQLAlchemyError, InvalidRequestError, DBAPIError, OperationalError, ProgrammingError
|
from sqlalchemy.exc import SQLAlchemyError, InvalidRequestError, DBAPIError, OperationalError, ProgrammingError
|
||||||
from sqlalchemy.orm import scoped_session, sessionmaker, mapper
|
from sqlalchemy.orm import scoped_session, sessionmaker, mapper
|
||||||
|
@ -37,7 +38,8 @@ from sqlalchemy.pool import NullPool
|
||||||
from alembic.migration import MigrationContext
|
from alembic.migration import MigrationContext
|
||||||
from alembic.operations import Operations
|
from alembic.operations import Operations
|
||||||
|
|
||||||
from openlp.core.common import AppLocation, Settings, translate, delete_file
|
from openlp.core.common import AppLocation, Settings, delete_file, translate
|
||||||
|
from openlp.core.common.json import OpenLPJsonDecoder, OpenLPJsonEncoder
|
||||||
from openlp.core.lib.ui import critical_error_message_box
|
from openlp.core.lib.ui import critical_error_message_box
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
@ -133,9 +135,10 @@ def get_db_path(plugin_name, db_file_name=None):
|
||||||
if db_file_name is None:
|
if db_file_name is None:
|
||||||
return 'sqlite:///{path}/{plugin}.sqlite'.format(path=AppLocation.get_section_data_path(plugin_name),
|
return 'sqlite:///{path}/{plugin}.sqlite'.format(path=AppLocation.get_section_data_path(plugin_name),
|
||||||
plugin=plugin_name)
|
plugin=plugin_name)
|
||||||
|
elif os.path.isabs(db_file_name):
|
||||||
|
return 'sqlite:///{db_file_name}'.format(db_file_name=db_file_name)
|
||||||
else:
|
else:
|
||||||
return 'sqlite:///{path}/{name}'.format(path=AppLocation.get_section_data_path(plugin_name),
|
return 'sqlite:///{path}/{name}'.format(path=AppLocation.get_section_data_path(plugin_name), name=db_file_name)
|
||||||
name=db_file_name)
|
|
||||||
|
|
||||||
|
|
||||||
def handle_db_error(plugin_name, db_file_name):
|
def handle_db_error(plugin_name, db_file_name):
|
||||||
|
@ -200,6 +203,55 @@ class BaseModel(object):
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
|
|
||||||
|
class PathType(types.TypeDecorator):
|
||||||
|
"""
|
||||||
|
Create a PathType for storing Path objects with SQLAlchemy. Behind the scenes we convert the Path object to a JSON
|
||||||
|
representation and store it as a Unicode type
|
||||||
|
"""
|
||||||
|
impl = types.UnicodeText
|
||||||
|
|
||||||
|
def coerce_compared_value(self, op, value):
|
||||||
|
"""
|
||||||
|
Some times it make sense to compare a PathType with a string. In the case a string is used coerce the the
|
||||||
|
PathType to a UnicodeText type.
|
||||||
|
|
||||||
|
:param op: The operation being carried out. Not used, as we only care about the type that is being used with the
|
||||||
|
operation.
|
||||||
|
:param openlp.core.common.path.Path | str value: The value being used for the comparison. Most likely a Path
|
||||||
|
Object or str.
|
||||||
|
:return: The coerced value stored in the db
|
||||||
|
:rtype: PathType or UnicodeText
|
||||||
|
"""
|
||||||
|
if isinstance(value, str):
|
||||||
|
return UnicodeText()
|
||||||
|
else:
|
||||||
|
return self
|
||||||
|
|
||||||
|
def process_bind_param(self, value, dialect):
|
||||||
|
"""
|
||||||
|
Convert the Path object to a JSON representation
|
||||||
|
|
||||||
|
:param openlp.core.common.path.Path value: The value to convert
|
||||||
|
:param dialect: Not used
|
||||||
|
:return: The Path object as a JSON string
|
||||||
|
:rtype: str
|
||||||
|
"""
|
||||||
|
data_path = AppLocation.get_data_path()
|
||||||
|
return json.dumps(value, cls=OpenLPJsonEncoder, base_path=data_path)
|
||||||
|
|
||||||
|
def process_result_value(self, value, dialect):
|
||||||
|
"""
|
||||||
|
Convert the JSON representation back
|
||||||
|
|
||||||
|
:param types.UnicodeText value: The value to convert
|
||||||
|
:param dialect: Not used
|
||||||
|
:return: The JSON object converted Python object (in this case it should be a Path object)
|
||||||
|
:rtype: openlp.core.common.path.Path
|
||||||
|
"""
|
||||||
|
data_path = AppLocation.get_data_path()
|
||||||
|
return json.loads(value, cls=OpenLPJsonDecoder, base_path=data_path)
|
||||||
|
|
||||||
|
|
||||||
def upgrade_db(url, upgrade):
|
def upgrade_db(url, upgrade):
|
||||||
"""
|
"""
|
||||||
Upgrade a database.
|
Upgrade a database.
|
||||||
|
@ -208,7 +260,7 @@ def upgrade_db(url, upgrade):
|
||||||
:param upgrade: The python module that contains the upgrade instructions.
|
:param upgrade: The python module that contains the upgrade instructions.
|
||||||
"""
|
"""
|
||||||
if not database_exists(url):
|
if not database_exists(url):
|
||||||
log.warn("Database {db} doesn't exist - skipping upgrade checks".format(db=url))
|
log.warning("Database {db} doesn't exist - skipping upgrade checks".format(db=url))
|
||||||
return (0, 0)
|
return (0, 0)
|
||||||
|
|
||||||
log.debug('Checking upgrades for DB {db}'.format(db=url))
|
log.debug('Checking upgrades for DB {db}'.format(db=url))
|
||||||
|
@ -273,10 +325,11 @@ def delete_database(plugin_name, db_file_name=None):
|
||||||
:param plugin_name: The name of the plugin to remove the database for
|
:param plugin_name: The name of the plugin to remove the database for
|
||||||
:param db_file_name: The database file name. Defaults to None resulting in the plugin_name being used.
|
:param db_file_name: The database file name. Defaults to None resulting in the plugin_name being used.
|
||||||
"""
|
"""
|
||||||
|
db_file_path = AppLocation.get_section_data_path(plugin_name)
|
||||||
if db_file_name:
|
if db_file_name:
|
||||||
db_file_path = AppLocation.get_section_data_path(plugin_name) / db_file_name
|
db_file_path = db_file_path / db_file_name
|
||||||
else:
|
else:
|
||||||
db_file_path = AppLocation.get_section_data_path(plugin_name) / plugin_name
|
db_file_path = db_file_path / plugin_name
|
||||||
return delete_file(db_file_path)
|
return delete_file(db_file_path)
|
||||||
|
|
||||||
|
|
||||||
|
@ -284,30 +337,30 @@ class Manager(object):
|
||||||
"""
|
"""
|
||||||
Provide generic object persistence management
|
Provide generic object persistence management
|
||||||
"""
|
"""
|
||||||
def __init__(self, plugin_name, init_schema, db_file_name=None, upgrade_mod=None, session=None):
|
def __init__(self, plugin_name, init_schema, db_file_path=None, upgrade_mod=None, session=None):
|
||||||
"""
|
"""
|
||||||
Runs the initialisation process that includes creating the connection to the database and the tables if they do
|
Runs the initialisation process that includes creating the connection to the database and the tables if they do
|
||||||
not exist.
|
not exist.
|
||||||
|
|
||||||
:param plugin_name: The name to setup paths and settings section names
|
:param plugin_name: The name to setup paths and settings section names
|
||||||
:param init_schema: The init_schema function for this database
|
:param init_schema: The init_schema function for this database
|
||||||
:param db_file_name: The upgrade_schema function for this database
|
:param openlp.core.common.path.Path db_file_path: The file name to use for this database. Defaults to None
|
||||||
:param upgrade_mod: The file name to use for this database. Defaults to None resulting in the plugin_name
|
resulting in the plugin_name being used.
|
||||||
being used.
|
:param upgrade_mod: The upgrade_schema function for this database
|
||||||
"""
|
"""
|
||||||
self.is_dirty = False
|
self.is_dirty = False
|
||||||
self.session = None
|
self.session = None
|
||||||
self.db_url = None
|
self.db_url = None
|
||||||
if db_file_name:
|
if db_file_path:
|
||||||
log.debug('Manager: Creating new DB url')
|
log.debug('Manager: Creating new DB url')
|
||||||
self.db_url = init_url(plugin_name, db_file_name)
|
self.db_url = init_url(plugin_name, str(db_file_path))
|
||||||
else:
|
else:
|
||||||
self.db_url = init_url(plugin_name)
|
self.db_url = init_url(plugin_name)
|
||||||
if upgrade_mod:
|
if upgrade_mod:
|
||||||
try:
|
try:
|
||||||
db_ver, up_ver = upgrade_db(self.db_url, upgrade_mod)
|
db_ver, up_ver = upgrade_db(self.db_url, upgrade_mod)
|
||||||
except (SQLAlchemyError, DBAPIError):
|
except (SQLAlchemyError, DBAPIError):
|
||||||
handle_db_error(plugin_name, db_file_name)
|
handle_db_error(plugin_name, str(db_file_path))
|
||||||
return
|
return
|
||||||
if db_ver > up_ver:
|
if db_ver > up_ver:
|
||||||
critical_error_message_box(
|
critical_error_message_box(
|
||||||
|
@ -322,7 +375,7 @@ class Manager(object):
|
||||||
try:
|
try:
|
||||||
self.session = init_schema(self.db_url)
|
self.session = init_schema(self.db_url)
|
||||||
except (SQLAlchemyError, DBAPIError):
|
except (SQLAlchemyError, DBAPIError):
|
||||||
handle_db_error(plugin_name, db_file_name)
|
handle_db_error(plugin_name, str(db_file_path))
|
||||||
else:
|
else:
|
||||||
self.session = session
|
self.session = session
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
"color": "#000000",
|
"color": "#000000",
|
||||||
"direction": "vertical",
|
"direction": "vertical",
|
||||||
"end_color": "#000000",
|
"end_color": "#000000",
|
||||||
"filename": "",
|
"filename": null,
|
||||||
"start_color": "#000000",
|
"start_color": "#000000",
|
||||||
"type": "solid"
|
"type": "solid"
|
||||||
},
|
},
|
||||||
|
|
|
@ -359,10 +359,8 @@ class MediaManagerItem(QtWidgets.QWidget, RegistryProperties):
|
||||||
:param files: The files to be loaded.
|
:param files: The files to be loaded.
|
||||||
:param target_group: The QTreeWidgetItem of the group that will be the parent of the added files
|
:param target_group: The QTreeWidgetItem of the group that will be the parent of the added files
|
||||||
"""
|
"""
|
||||||
names = []
|
|
||||||
full_list = []
|
full_list = []
|
||||||
for count in range(self.list_view.count()):
|
for count in range(self.list_view.count()):
|
||||||
names.append(self.list_view.item(count).text())
|
|
||||||
full_list.append(self.list_view.item(count).data(QtCore.Qt.UserRole))
|
full_list.append(self.list_view.item(count).data(QtCore.Qt.UserRole))
|
||||||
duplicates_found = False
|
duplicates_found = False
|
||||||
files_added = False
|
files_added = False
|
||||||
|
|
|
@ -27,7 +27,7 @@ import logging
|
||||||
from PyQt5 import QtCore
|
from PyQt5 import QtCore
|
||||||
|
|
||||||
from openlp.core.common import Registry, RegistryProperties, Settings, UiStrings
|
from openlp.core.common import Registry, RegistryProperties, Settings, UiStrings
|
||||||
from openlp.core.common.versionchecker import get_application_version
|
from openlp.core.version import get_version
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -139,7 +139,7 @@ class Plugin(QtCore.QObject, RegistryProperties):
|
||||||
if version:
|
if version:
|
||||||
self.version = version
|
self.version = version
|
||||||
else:
|
else:
|
||||||
self.version = get_application_version()['version']
|
self.version = get_version()['version']
|
||||||
self.settings_section = self.name
|
self.settings_section = self.name
|
||||||
self.icon = None
|
self.icon = None
|
||||||
self.media_item_class = media_item_class
|
self.media_item_class = media_item_class
|
||||||
|
|
|
@ -341,9 +341,9 @@ class ProjectorDB(Manager):
|
||||||
"""
|
"""
|
||||||
old_projector = self.get_object_filtered(Projector, Projector.ip == projector.ip)
|
old_projector = self.get_object_filtered(Projector, Projector.ip == projector.ip)
|
||||||
if old_projector is not None:
|
if old_projector is not None:
|
||||||
log.warning('add_new() skipping entry ip="{ip}" (Already saved)'.format(ip=old_projector.ip))
|
log.warning('add_projector() skipping entry ip="{ip}" (Already saved)'.format(ip=old_projector.ip))
|
||||||
return False
|
return False
|
||||||
log.debug('add_new() saving new entry')
|
log.debug('add_projector() saving new entry')
|
||||||
log.debug('ip="{ip}", name="{name}", location="{location}"'.format(ip=projector.ip,
|
log.debug('ip="{ip}", name="{name}", location="{location}"'.format(ip=projector.ip,
|
||||||
name=projector.name,
|
name=projector.name,
|
||||||
location=projector.location))
|
location=projector.location))
|
||||||
|
|
|
@ -72,6 +72,28 @@ PJLINK_HEADER = '{prefix}{{linkclass}}'.format(prefix=PJLINK_PREFIX)
|
||||||
PJLINK_SUFFIX = CR
|
PJLINK_SUFFIX = CR
|
||||||
|
|
||||||
|
|
||||||
|
class PJLinkUDP(QtNetwork.QUdpSocket):
|
||||||
|
"""
|
||||||
|
Socket service for PJLink UDP socket.
|
||||||
|
"""
|
||||||
|
# New commands available in PJLink Class 2
|
||||||
|
pjlink_udp_commands = [
|
||||||
|
'ACKN', # Class 2 (cmd is SRCH)
|
||||||
|
'ERST', # Class 1/2
|
||||||
|
'INPT', # Class 1/2
|
||||||
|
'LKUP', # Class 2 (reply only - no cmd)
|
||||||
|
'POWR', # Class 1/2
|
||||||
|
'SRCH' # Class 2 (reply is ACKN)
|
||||||
|
]
|
||||||
|
|
||||||
|
def __init__(self, port=PJLINK_PORT):
|
||||||
|
"""
|
||||||
|
Initialize socket
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.port = port
|
||||||
|
|
||||||
|
|
||||||
class PJLinkCommands(object):
|
class PJLinkCommands(object):
|
||||||
"""
|
"""
|
||||||
Process replies from PJLink projector.
|
Process replies from PJLink projector.
|
||||||
|
@ -488,7 +510,7 @@ class PJLinkCommands(object):
|
||||||
|
|
||||||
class PJLink(PJLinkCommands, QtNetwork.QTcpSocket):
|
class PJLink(PJLinkCommands, QtNetwork.QTcpSocket):
|
||||||
"""
|
"""
|
||||||
Socket service for connecting to a PJLink-capable projector.
|
Socket service for PJLink TCP socket.
|
||||||
"""
|
"""
|
||||||
# Signals sent by this module
|
# Signals sent by this module
|
||||||
changeStatus = QtCore.pyqtSignal(str, int, str)
|
changeStatus = QtCore.pyqtSignal(str, int, str)
|
||||||
|
@ -499,43 +521,29 @@ class PJLink(PJLinkCommands, QtNetwork.QTcpSocket):
|
||||||
projectorReceivedData = QtCore.pyqtSignal() # Notify when received data finished processing
|
projectorReceivedData = QtCore.pyqtSignal() # Notify when received data finished processing
|
||||||
projectorUpdateIcons = QtCore.pyqtSignal() # Update the status icons on toolbar
|
projectorUpdateIcons = QtCore.pyqtSignal() # Update the status icons on toolbar
|
||||||
|
|
||||||
# New commands available in PJLink Class 2
|
def __init__(self, projector, *args, **kwargs):
|
||||||
pjlink_udp_commands = [
|
|
||||||
'ACKN', # Class 2
|
|
||||||
'ERST', # Class 1 or 2
|
|
||||||
'INPT', # Class 1 or 2
|
|
||||||
'LKUP', # Class 2
|
|
||||||
'POWR', # Class 1 or 2
|
|
||||||
'SRCH' # Class 2
|
|
||||||
]
|
|
||||||
|
|
||||||
def __init__(self, port=PJLINK_PORT, *args, **kwargs):
|
|
||||||
"""
|
"""
|
||||||
Setup for instance.
|
Setup for instance.
|
||||||
Options should be in kwargs except for port which does have a default.
|
Options should be in kwargs except for port which does have a default.
|
||||||
|
|
||||||
:param name: Display name
|
:param projector: Database record of projector
|
||||||
:param ip: IP address to connect to
|
|
||||||
:param port: Port to use. Default to PJLINK_PORT
|
|
||||||
:param pin: Access pin (if needed)
|
|
||||||
|
|
||||||
Optional parameters
|
Optional parameters
|
||||||
:param dbid: Database ID number
|
|
||||||
:param location: Location where projector is physically located
|
|
||||||
:param notes: Extra notes about the projector
|
|
||||||
:param poll_time: Time (in seconds) to poll connected projector
|
:param poll_time: Time (in seconds) to poll connected projector
|
||||||
:param socket_timeout: Time (in seconds) to abort the connection if no response
|
:param socket_timeout: Time (in seconds) to abort the connection if no response
|
||||||
"""
|
"""
|
||||||
log.debug('PJlink(args={args} kwargs={kwargs})'.format(args=args, kwargs=kwargs))
|
log.debug('PJlink(projector={projector}, args={args} kwargs={kwargs})'.format(projector=projector,
|
||||||
|
args=args,
|
||||||
|
kwargs=kwargs))
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.dbid = kwargs.get('dbid')
|
self.entry = projector
|
||||||
self.ip = kwargs.get('ip')
|
self.ip = self.entry.ip
|
||||||
self.location = kwargs.get('location')
|
self.location = self.entry.location
|
||||||
self.mac_adx = kwargs.get('mac_adx')
|
self.mac_adx = self.entry.mac_adx
|
||||||
self.name = kwargs.get('name')
|
self.name = self.entry.name
|
||||||
self.notes = kwargs.get('notes')
|
self.notes = self.entry.notes
|
||||||
self.pin = kwargs.get('pin')
|
self.pin = self.entry.pin
|
||||||
self.port = port
|
self.port = self.entry.port
|
||||||
self.db_update = False # Use to check if db needs to be updated prior to exiting
|
self.db_update = False # Use to check if db needs to be updated prior to exiting
|
||||||
# Poll time 20 seconds unless called with something else
|
# Poll time 20 seconds unless called with something else
|
||||||
self.poll_time = 20000 if 'poll_time' not in kwargs else kwargs['poll_time'] * 1000
|
self.poll_time = 20000 if 'poll_time' not in kwargs else kwargs['poll_time'] * 1000
|
||||||
|
@ -751,7 +759,7 @@ class PJLink(PJLinkCommands, QtNetwork.QTcpSocket):
|
||||||
self.change_status(E_AUTHENTICATION)
|
self.change_status(E_AUTHENTICATION)
|
||||||
log.debug('({ip}) emitting projectorAuthentication() signal'.format(ip=self.ip))
|
log.debug('({ip}) emitting projectorAuthentication() signal'.format(ip=self.ip))
|
||||||
return
|
return
|
||||||
elif data_check[1] == '0' and self.pin is not None:
|
elif (data_check[1] == '0') and (self.pin):
|
||||||
# Pin set and no authentication needed
|
# Pin set and no authentication needed
|
||||||
log.warning('({ip}) Regular connection but PIN set'.format(ip=self.name))
|
log.warning('({ip}) Regular connection but PIN set'.format(ip=self.name))
|
||||||
self.disconnect_from_host()
|
self.disconnect_from_host()
|
||||||
|
@ -761,7 +769,7 @@ class PJLink(PJLinkCommands, QtNetwork.QTcpSocket):
|
||||||
return
|
return
|
||||||
elif data_check[1] == '1':
|
elif data_check[1] == '1':
|
||||||
# Authenticated login with salt
|
# Authenticated login with salt
|
||||||
if self.pin is None:
|
if not self.pin:
|
||||||
log.warning('({ip}) Authenticated connection but no pin set'.format(ip=self.ip))
|
log.warning('({ip}) Authenticated connection but no pin set'.format(ip=self.ip))
|
||||||
self.disconnect_from_host()
|
self.disconnect_from_host()
|
||||||
self.change_status(E_AUTHENTICATION)
|
self.change_status(E_AUTHENTICATION)
|
||||||
|
@ -776,7 +784,7 @@ class PJLink(PJLinkCommands, QtNetwork.QTcpSocket):
|
||||||
else:
|
else:
|
||||||
data_hash = None
|
data_hash = None
|
||||||
# We're connected at this point, so go ahead and setup regular I/O
|
# We're connected at this point, so go ahead and setup regular I/O
|
||||||
self.readyRead.connect(self.get_data)
|
self.readyRead.connect(self.get_socket)
|
||||||
self.projectorReceivedData.connect(self._send_command)
|
self.projectorReceivedData.connect(self._send_command)
|
||||||
# Initial data we should know about
|
# Initial data we should know about
|
||||||
self.send_command(cmd='CLSS', salt=data_hash)
|
self.send_command(cmd='CLSS', salt=data_hash)
|
||||||
|
@ -800,27 +808,51 @@ class PJLink(PJLinkCommands, QtNetwork.QTcpSocket):
|
||||||
count=trash_count))
|
count=trash_count))
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@QtCore.pyqtSlot(str, str)
|
||||||
|
def get_buffer(self, data, ip):
|
||||||
|
"""
|
||||||
|
Get data from somewhere other than TCP socket
|
||||||
|
|
||||||
|
:param data: Data to process. buffer must be formatted as a proper PJLink packet.
|
||||||
|
:param ip: Destination IP for buffer.
|
||||||
|
"""
|
||||||
|
log.debug("({ip}) get_buffer(data='{buff}' ip='{ip_in}'".format(ip=self.ip, buff=data, ip_in=ip))
|
||||||
|
if ip is None:
|
||||||
|
log.debug("({ip}) get_buffer() Don't know who data is for - exiting".format(ip=self.ip))
|
||||||
|
return
|
||||||
|
return self.get_data(buff=data, ip=ip)
|
||||||
|
|
||||||
@QtCore.pyqtSlot()
|
@QtCore.pyqtSlot()
|
||||||
def get_data(self):
|
def get_socket(self):
|
||||||
"""
|
"""
|
||||||
Socket interface to retrieve data.
|
Get data from TCP socket.
|
||||||
"""
|
"""
|
||||||
log.debug('({ip}) get_data(): Reading data'.format(ip=self.ip))
|
log.debug('({ip}) get_socket(): Reading data'.format(ip=self.ip))
|
||||||
if self.state() != self.ConnectedState:
|
if self.state() != self.ConnectedState:
|
||||||
log.debug('({ip}) get_data(): Not connected - returning'.format(ip=self.ip))
|
log.debug('({ip}) get_socket(): Not connected - returning'.format(ip=self.ip))
|
||||||
self.send_busy = False
|
self.send_busy = False
|
||||||
return
|
return
|
||||||
# Although we have a packet length limit, go ahead and use a larger buffer
|
# Although we have a packet length limit, go ahead and use a larger buffer
|
||||||
read = self.readLine(1024)
|
read = self.readLine(1024)
|
||||||
log.debug("({ip}) get_data(): '{buff}'".format(ip=self.ip, buff=read))
|
log.debug("({ip}) get_socket(): '{buff}'".format(ip=self.ip, buff=read))
|
||||||
if read == -1:
|
if read == -1:
|
||||||
# No data available
|
# No data available
|
||||||
log.debug('({ip}) get_data(): No data available (-1)'.format(ip=self.ip))
|
log.debug('({ip}) get_socket(): No data available (-1)'.format(ip=self.ip))
|
||||||
return self.receive_data_signal()
|
return self.receive_data_signal()
|
||||||
self.socket_timer.stop()
|
self.socket_timer.stop()
|
||||||
self.projectorNetwork.emit(S_NETWORK_RECEIVED)
|
self.projectorNetwork.emit(S_NETWORK_RECEIVED)
|
||||||
|
return self.get_data(buff=read, ip=self.ip)
|
||||||
|
|
||||||
|
def get_data(self, buff, ip):
|
||||||
|
"""
|
||||||
|
Process received data
|
||||||
|
|
||||||
|
:param buff: Data to process.
|
||||||
|
:param ip: (optional) Destination IP.
|
||||||
|
"""
|
||||||
|
log.debug("({ip}) get_data(ip='{ip_in}' buffer='{buff}'".format(ip=self.ip, ip_in=ip, buff=buff))
|
||||||
# NOTE: Class2 has changed to some values being UTF-8
|
# NOTE: Class2 has changed to some values being UTF-8
|
||||||
data_in = decode(read, 'utf-8')
|
data_in = decode(buff, 'utf-8')
|
||||||
data = data_in.strip()
|
data = data_in.strip()
|
||||||
if (len(data) < 7) or (not data.startswith(PJLINK_PREFIX)):
|
if (len(data) < 7) or (not data.startswith(PJLINK_PREFIX)):
|
||||||
return self._trash_buffer(msg='get_data(): Invalid packet - length or prefix')
|
return self._trash_buffer(msg='get_data(): Invalid packet - length or prefix')
|
||||||
|
@ -990,7 +1022,7 @@ class PJLink(PJLinkCommands, QtNetwork.QTcpSocket):
|
||||||
self.reset_information()
|
self.reset_information()
|
||||||
self.disconnectFromHost()
|
self.disconnectFromHost()
|
||||||
try:
|
try:
|
||||||
self.readyRead.disconnect(self.get_data)
|
self.readyRead.disconnect(self.get_socket)
|
||||||
except TypeError:
|
except TypeError:
|
||||||
pass
|
pass
|
||||||
if abort:
|
if abort:
|
||||||
|
|
|
@ -1,85 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
|
||||||
|
|
||||||
###############################################################################
|
|
||||||
# OpenLP - Open Source Lyrics Projection #
|
|
||||||
# --------------------------------------------------------------------------- #
|
|
||||||
# Copyright (c) 2008-2017 OpenLP Developers #
|
|
||||||
# --------------------------------------------------------------------------- #
|
|
||||||
# This program is free software; you can redistribute it and/or modify it #
|
|
||||||
# under the terms of the GNU General Public License as published by the Free #
|
|
||||||
# Software Foundation; version 2 of the License. #
|
|
||||||
# #
|
|
||||||
# This program is distributed in the hope that it will be useful, but WITHOUT #
|
|
||||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
|
|
||||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
|
|
||||||
# more details. #
|
|
||||||
# #
|
|
||||||
# You should have received a copy of the GNU General Public License along #
|
|
||||||
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
|
||||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
|
||||||
###############################################################################
|
|
||||||
"""
|
|
||||||
:mod:`openlp.core.lib.projector.pjlink2` module provides the PJLink Class 2
|
|
||||||
updates from PJLink Class 1.
|
|
||||||
|
|
||||||
This module only handles the UDP socket functionality. Command/query/status
|
|
||||||
change messages will still be processed by the PJLink 1 module.
|
|
||||||
|
|
||||||
Currently, the only variance is the addition of a UDP "search" command to
|
|
||||||
query the local network for Class 2 capable projectors,
|
|
||||||
and UDP "notify" messages from projectors to connected software of status
|
|
||||||
changes (i.e., power change, input change, error changes).
|
|
||||||
|
|
||||||
Differences between Class 1 and Class 2 PJLink specifications are as follows.
|
|
||||||
|
|
||||||
New Functionality:
|
|
||||||
* Search - UDP Query local network for Class 2 capabable projector(s).
|
|
||||||
* Status - UDP Status change with connected projector(s). Status change
|
|
||||||
messages consist of:
|
|
||||||
* Initial projector power up when network communication becomes available
|
|
||||||
* Lamp off/standby to warmup or on
|
|
||||||
* Lamp on to cooldown or off/standby
|
|
||||||
* Input source select change completed
|
|
||||||
* Error status change (i.e., fan/lamp/temp/cover open/filter/other error(s))
|
|
||||||
|
|
||||||
New Commands:
|
|
||||||
* Query serial number of projector
|
|
||||||
* Query version number of projector software
|
|
||||||
* Query model number of replacement lamp
|
|
||||||
* Query model number of replacement air filter
|
|
||||||
* Query current projector screen resolution
|
|
||||||
* Query recommended screen resolution
|
|
||||||
* Query name of specific input terminal (video source)
|
|
||||||
* Adjust projector microphone in 1-step increments
|
|
||||||
* Adjust projector speacker in 1-step increments
|
|
||||||
|
|
||||||
Extended Commands:
|
|
||||||
* Addition of INTERNAL terminal (video source) for a total of 6 types of terminals.
|
|
||||||
* Number of terminals (video source) has been expanded from [1-9]
|
|
||||||
to [1-9a-z] (Addition of 26 terminals for each type of input).
|
|
||||||
|
|
||||||
See PJLink Class 2 Specifications for details.
|
|
||||||
http://pjlink.jbmia.or.jp/english/dl_class2.html
|
|
||||||
|
|
||||||
Section 5-1 PJLink Specifications
|
|
||||||
|
|
||||||
Section 5-5 Guidelines for Input Terminals
|
|
||||||
"""
|
|
||||||
import logging
|
|
||||||
log = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
log.debug('pjlink2 loaded')
|
|
||||||
|
|
||||||
from PyQt5 import QtNetwork
|
|
||||||
|
|
||||||
|
|
||||||
class PJLinkUDP(QtNetwork.QUdpSocket):
|
|
||||||
"""
|
|
||||||
Socket service for handling datagram (UDP) sockets.
|
|
||||||
"""
|
|
||||||
log.debug('PJLinkUDP loaded')
|
|
||||||
# Class varialbe for projector list. Should be replaced by ProjectorManager's
|
|
||||||
# projector list after being loaded there.
|
|
||||||
projector_list = None
|
|
||||||
projectors_found = None # UDP search found list
|
|
|
@ -26,6 +26,7 @@ from string import Template
|
||||||
from PyQt5 import QtGui, QtCore, QtWebKitWidgets
|
from PyQt5 import QtGui, QtCore, QtWebKitWidgets
|
||||||
|
|
||||||
from openlp.core.common import Registry, RegistryProperties, OpenLPMixin, RegistryMixin, Settings
|
from openlp.core.common import Registry, RegistryProperties, OpenLPMixin, RegistryMixin, Settings
|
||||||
|
from openlp.core.common.path import path_to_str
|
||||||
from openlp.core.lib import FormattingTags, ImageSource, ItemCapabilities, ScreenList, ServiceItem, expand_tags, \
|
from openlp.core.lib import FormattingTags, ImageSource, ItemCapabilities, ScreenList, ServiceItem, expand_tags, \
|
||||||
build_lyrics_format_css, build_lyrics_outline_css, build_chords_css
|
build_lyrics_format_css, build_lyrics_outline_css, build_chords_css
|
||||||
from openlp.core.common import ThemeLevel
|
from openlp.core.common import ThemeLevel
|
||||||
|
@ -118,7 +119,7 @@ class Renderer(OpenLPMixin, RegistryMixin, RegistryProperties):
|
||||||
theme_data, main_rect, footer_rect = self._theme_dimensions[theme_name]
|
theme_data, main_rect, footer_rect = self._theme_dimensions[theme_name]
|
||||||
# if No file do not update cache
|
# if No file do not update cache
|
||||||
if theme_data.background_filename:
|
if theme_data.background_filename:
|
||||||
self.image_manager.add_image(theme_data.background_filename,
|
self.image_manager.add_image(path_to_str(theme_data.background_filename),
|
||||||
ImageSource.Theme, QtGui.QColor(theme_data.background_border_color))
|
ImageSource.Theme, QtGui.QColor(theme_data.background_border_color))
|
||||||
|
|
||||||
def pre_render(self, override_theme_data=None):
|
def pre_render(self, override_theme_data=None):
|
||||||
|
@ -207,8 +208,8 @@ class Renderer(OpenLPMixin, RegistryMixin, RegistryProperties):
|
||||||
service_item.raw_footer = FOOTER
|
service_item.raw_footer = FOOTER
|
||||||
# if No file do not update cache
|
# if No file do not update cache
|
||||||
if theme_data.background_filename:
|
if theme_data.background_filename:
|
||||||
self.image_manager.add_image(
|
self.image_manager.add_image(path_to_str(theme_data.background_filename),
|
||||||
theme_data.background_filename, ImageSource.Theme, QtGui.QColor(theme_data.background_border_color))
|
ImageSource.Theme, QtGui.QColor(theme_data.background_border_color))
|
||||||
theme_data, main, footer = self.pre_render(theme_data)
|
theme_data, main, footer = self.pre_render(theme_data)
|
||||||
service_item.theme_data = theme_data
|
service_item.theme_data = theme_data
|
||||||
service_item.main = main
|
service_item.main = main
|
||||||
|
|
|
@ -22,13 +22,13 @@
|
||||||
"""
|
"""
|
||||||
Provide the theme XML and handling functions for OpenLP v2 themes.
|
Provide the theme XML and handling functions for OpenLP v2 themes.
|
||||||
"""
|
"""
|
||||||
import os
|
|
||||||
import logging
|
|
||||||
import json
|
import json
|
||||||
|
import logging
|
||||||
|
|
||||||
from lxml import etree, objectify
|
from lxml import etree, objectify
|
||||||
from openlp.core.common import AppLocation, de_hump
|
from openlp.core.common import AppLocation, de_hump
|
||||||
|
from openlp.core.common.json import OpenLPJsonDecoder, OpenLPJsonEncoder
|
||||||
|
from openlp.core.common.path import Path, str_to_path
|
||||||
from openlp.core.lib import str_to_bool, ScreenList, get_text_file_string
|
from openlp.core.lib import str_to_bool, ScreenList, get_text_file_string
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
@ -160,9 +160,8 @@ class Theme(object):
|
||||||
# basic theme object with defaults
|
# basic theme object with defaults
|
||||||
json_path = AppLocation.get_directory(AppLocation.AppDir) / 'core' / 'lib' / 'json' / 'theme.json'
|
json_path = AppLocation.get_directory(AppLocation.AppDir) / 'core' / 'lib' / 'json' / 'theme.json'
|
||||||
jsn = get_text_file_string(json_path)
|
jsn = get_text_file_string(json_path)
|
||||||
jsn = json.loads(jsn)
|
self.load_theme(jsn)
|
||||||
self.expand_json(jsn)
|
self.background_filename = None
|
||||||
self.background_filename = ''
|
|
||||||
|
|
||||||
def expand_json(self, var, prev=None):
|
def expand_json(self, var, prev=None):
|
||||||
"""
|
"""
|
||||||
|
@ -174,8 +173,6 @@ class Theme(object):
|
||||||
for key, value in var.items():
|
for key, value in var.items():
|
||||||
if prev:
|
if prev:
|
||||||
key = prev + "_" + key
|
key = prev + "_" + key
|
||||||
else:
|
|
||||||
key = key
|
|
||||||
if isinstance(value, dict):
|
if isinstance(value, dict):
|
||||||
self.expand_json(value, key)
|
self.expand_json(value, key)
|
||||||
else:
|
else:
|
||||||
|
@ -185,13 +182,13 @@ class Theme(object):
|
||||||
"""
|
"""
|
||||||
Add the path name to the image name so the background can be rendered.
|
Add the path name to the image name so the background can be rendered.
|
||||||
|
|
||||||
:param path: The path name to be added.
|
:param openlp.core.common.path.Path path: The path name to be added.
|
||||||
|
:rtype: None
|
||||||
"""
|
"""
|
||||||
if self.background_type == 'image' or self.background_type == 'video':
|
if self.background_type == 'image' or self.background_type == 'video':
|
||||||
if self.background_filename and path:
|
if self.background_filename and path:
|
||||||
self.theme_name = self.theme_name.strip()
|
self.theme_name = self.theme_name.strip()
|
||||||
self.background_filename = self.background_filename.strip()
|
self.background_filename = path / self.theme_name / self.background_filename
|
||||||
self.background_filename = os.path.join(path, self.theme_name, self.background_filename)
|
|
||||||
|
|
||||||
def set_default_header_footer(self):
|
def set_default_header_footer(self):
|
||||||
"""
|
"""
|
||||||
|
@ -206,16 +203,21 @@ class Theme(object):
|
||||||
self.font_footer_y = current_screen['size'].height() * 9 / 10
|
self.font_footer_y = current_screen['size'].height() * 9 / 10
|
||||||
self.font_footer_height = current_screen['size'].height() / 10
|
self.font_footer_height = current_screen['size'].height() / 10
|
||||||
|
|
||||||
def load_theme(self, theme):
|
def load_theme(self, theme, theme_path=None):
|
||||||
"""
|
"""
|
||||||
Convert the JSON file and expand it.
|
Convert the JSON file and expand it.
|
||||||
|
|
||||||
:param theme: the theme string
|
:param theme: the theme string
|
||||||
|
:param openlp.core.common.path.Path theme_path: The path to the theme
|
||||||
|
:rtype: None
|
||||||
"""
|
"""
|
||||||
jsn = json.loads(theme)
|
if theme_path:
|
||||||
|
jsn = json.loads(theme, cls=OpenLPJsonDecoder, base_path=theme_path)
|
||||||
|
else:
|
||||||
|
jsn = json.loads(theme, cls=OpenLPJsonDecoder)
|
||||||
self.expand_json(jsn)
|
self.expand_json(jsn)
|
||||||
|
|
||||||
def export_theme(self):
|
def export_theme(self, theme_path=None):
|
||||||
"""
|
"""
|
||||||
Loop through the fields and build a dictionary of them
|
Loop through the fields and build a dictionary of them
|
||||||
|
|
||||||
|
@ -223,7 +225,9 @@ class Theme(object):
|
||||||
theme_data = {}
|
theme_data = {}
|
||||||
for attr, value in self.__dict__.items():
|
for attr, value in self.__dict__.items():
|
||||||
theme_data["{attr}".format(attr=attr)] = value
|
theme_data["{attr}".format(attr=attr)] = value
|
||||||
return json.dumps(theme_data)
|
if theme_path:
|
||||||
|
return json.dumps(theme_data, cls=OpenLPJsonEncoder, base_path=theme_path)
|
||||||
|
return json.dumps(theme_data, cls=OpenLPJsonEncoder)
|
||||||
|
|
||||||
def parse(self, xml):
|
def parse(self, xml):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -20,45 +20,36 @@
|
||||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||||
###############################################################################
|
###############################################################################
|
||||||
"""
|
"""
|
||||||
Package to test the openlp.core.common.versionchecker package.
|
The :mod:`openlp.core.threading` module contains some common threading code
|
||||||
"""
|
"""
|
||||||
from unittest import TestCase
|
from PyQt5 import QtCore
|
||||||
from unittest.mock import MagicMock, patch
|
|
||||||
|
|
||||||
from openlp.core.common.settings import Settings
|
|
||||||
from openlp.core.common.versionchecker import VersionThread
|
|
||||||
|
|
||||||
from tests.helpers.testmixin import TestMixin
|
|
||||||
|
|
||||||
|
|
||||||
class TestVersionchecker(TestMixin, TestCase):
|
def run_thread(parent, worker, prefix='', auto_start=True):
|
||||||
|
"""
|
||||||
|
Create a thread and assign a worker to it. This removes a lot of boilerplate code from the codebase.
|
||||||
|
|
||||||
def setUp(self):
|
:param object parent: The parent object so that the thread and worker are not orphaned.
|
||||||
"""
|
:param QObject worker: A QObject-based worker object which does the actual work.
|
||||||
Create an instance and a few example actions.
|
:param str prefix: A prefix to be applied to the attribute names.
|
||||||
"""
|
:param bool auto_start: Automatically start the thread. Defaults to True.
|
||||||
self.build_settings()
|
"""
|
||||||
|
# Set up attribute names
|
||||||
def tearDown(self):
|
thread_name = 'thread'
|
||||||
"""
|
worker_name = 'worker'
|
||||||
Clean up
|
if prefix:
|
||||||
"""
|
thread_name = '_'.join([prefix, thread_name])
|
||||||
self.destroy_settings()
|
worker_name = '_'.join([prefix, worker_name])
|
||||||
|
# Create the thread and add the thread and the worker to the parent
|
||||||
def test_version_thread_triggered(self):
|
thread = QtCore.QThread()
|
||||||
"""
|
setattr(parent, thread_name, thread)
|
||||||
Test the version thread call does not trigger UI
|
setattr(parent, worker_name, worker)
|
||||||
:return:
|
# Move the worker into the thread's context
|
||||||
"""
|
worker.moveToThread(thread)
|
||||||
# GIVEN: a equal version setup and the data is not today.
|
# Connect slots and signals
|
||||||
mocked_main_window = MagicMock()
|
thread.started.connect(worker.start)
|
||||||
Settings().setValue('core/last version test', '1950-04-01')
|
worker.quit.connect(thread.quit)
|
||||||
# WHEN: We check to see if the version is different .
|
worker.quit.connect(worker.deleteLater)
|
||||||
with patch('PyQt5.QtCore.QThread'),\
|
thread.finished.connect(thread.deleteLater)
|
||||||
patch('openlp.core.common.versionchecker.get_application_version') as mocked_get_application_version:
|
if auto_start:
|
||||||
mocked_get_application_version.return_value = {'version': '1.0.0', 'build': '', 'full': '2.0.4'}
|
thread.start()
|
||||||
version_thread = VersionThread(mocked_main_window)
|
|
||||||
version_thread.run()
|
|
||||||
# THEN: If the version has changed the main window is notified
|
|
||||||
self.assertTrue(mocked_main_window.openlp_version_check.emit.called,
|
|
||||||
'The main windows should have been notified')
|
|
|
@ -26,7 +26,7 @@ import webbrowser
|
||||||
|
|
||||||
from PyQt5 import QtCore, QtWidgets
|
from PyQt5 import QtCore, QtWidgets
|
||||||
|
|
||||||
from openlp.core.common.versionchecker import get_application_version
|
from openlp.core.version import get_version
|
||||||
from openlp.core.lib import translate
|
from openlp.core.lib import translate
|
||||||
from .aboutdialog import UiAboutDialog
|
from .aboutdialog import UiAboutDialog
|
||||||
|
|
||||||
|
@ -49,7 +49,7 @@ class AboutForm(QtWidgets.QDialog, UiAboutDialog):
|
||||||
Set up the dialog. This method is mocked out in tests.
|
Set up the dialog. This method is mocked out in tests.
|
||||||
"""
|
"""
|
||||||
self.setup_ui(self)
|
self.setup_ui(self)
|
||||||
application_version = get_application_version()
|
application_version = get_version()
|
||||||
about_text = self.about_text_edit.toPlainText()
|
about_text = self.about_text_edit.toPlainText()
|
||||||
about_text = about_text.replace('<version>', application_version['version'])
|
about_text = about_text.replace('<version>', application_version['version'])
|
||||||
if application_version['build']:
|
if application_version['build']:
|
||||||
|
|
|
@ -22,9 +22,8 @@
|
||||||
"""
|
"""
|
||||||
The :mod:`advancedtab` provides an advanced settings facility.
|
The :mod:`advancedtab` provides an advanced settings facility.
|
||||||
"""
|
"""
|
||||||
from datetime import datetime, timedelta
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||||
|
|
||||||
|
@ -492,24 +491,27 @@ class AdvancedTab(SettingsTab):
|
||||||
self.service_name_edit.setText(UiStrings().DefaultServiceName)
|
self.service_name_edit.setText(UiStrings().DefaultServiceName)
|
||||||
self.service_name_edit.setFocus()
|
self.service_name_edit.setFocus()
|
||||||
|
|
||||||
def on_data_directory_path_edit_path_changed(self, new_data_path):
|
def on_data_directory_path_edit_path_changed(self, new_path):
|
||||||
"""
|
"""
|
||||||
Browse for a new data directory location.
|
Handle the `editPathChanged` signal of the data_directory_path_edit
|
||||||
|
|
||||||
|
:param openlp.core.common.path.Path new_path: The new path
|
||||||
|
:rtype: None
|
||||||
"""
|
"""
|
||||||
# Make sure they want to change the data.
|
# Make sure they want to change the data.
|
||||||
answer = QtWidgets.QMessageBox.question(self, translate('OpenLP.AdvancedTab', 'Confirm Data Directory Change'),
|
answer = QtWidgets.QMessageBox.question(self, translate('OpenLP.AdvancedTab', 'Confirm Data Directory Change'),
|
||||||
translate('OpenLP.AdvancedTab', 'Are you sure you want to change the '
|
translate('OpenLP.AdvancedTab', 'Are you sure you want to change the '
|
||||||
'location of the OpenLP data directory to:\n\n{path}'
|
'location of the OpenLP data directory to:\n\n{path}'
|
||||||
'\n\nThe data directory will be changed when OpenLP is '
|
'\n\nThe data directory will be changed when OpenLP is '
|
||||||
'closed.').format(path=new_data_path),
|
'closed.').format(path=new_path),
|
||||||
defaultButton=QtWidgets.QMessageBox.No)
|
defaultButton=QtWidgets.QMessageBox.No)
|
||||||
if answer != QtWidgets.QMessageBox.Yes:
|
if answer != QtWidgets.QMessageBox.Yes:
|
||||||
self.data_directory_path_edit.path = AppLocation.get_data_path()
|
self.data_directory_path_edit.path = AppLocation.get_data_path()
|
||||||
return
|
return
|
||||||
# Check if data already exists here.
|
# Check if data already exists here.
|
||||||
self.check_data_overwrite(path_to_str(new_data_path))
|
self.check_data_overwrite(new_path)
|
||||||
# Save the new location.
|
# Save the new location.
|
||||||
self.main_window.set_new_data_path(path_to_str(new_data_path))
|
self.main_window.new_data_path = new_path
|
||||||
self.data_directory_cancel_button.show()
|
self.data_directory_cancel_button.show()
|
||||||
|
|
||||||
def on_data_directory_copy_check_box_toggled(self):
|
def on_data_directory_copy_check_box_toggled(self):
|
||||||
|
@ -526,9 +528,10 @@ class AdvancedTab(SettingsTab):
|
||||||
def check_data_overwrite(self, data_path):
|
def check_data_overwrite(self, data_path):
|
||||||
"""
|
"""
|
||||||
Check if there's already data in the target directory.
|
Check if there's already data in the target directory.
|
||||||
|
|
||||||
|
:param openlp.core.common.path.Path data_path: The target directory to check
|
||||||
"""
|
"""
|
||||||
test_path = os.path.join(data_path, 'songs')
|
if (data_path / 'songs').exists():
|
||||||
if os.path.exists(test_path):
|
|
||||||
self.data_exists = True
|
self.data_exists = True
|
||||||
# Check is they want to replace existing data.
|
# Check is they want to replace existing data.
|
||||||
answer = QtWidgets.QMessageBox.warning(self,
|
answer = QtWidgets.QMessageBox.warning(self,
|
||||||
|
@ -537,7 +540,7 @@ class AdvancedTab(SettingsTab):
|
||||||
'WARNING: \n\nThe location you have selected \n\n{path}'
|
'WARNING: \n\nThe location you have selected \n\n{path}'
|
||||||
'\n\nappears to contain OpenLP data files. Do you wish to '
|
'\n\nappears to contain OpenLP data files. Do you wish to '
|
||||||
'replace these files with the current data '
|
'replace these files with the current data '
|
||||||
'files?').format(path=os.path.abspath(data_path,)),
|
'files?'.format(path=data_path)),
|
||||||
QtWidgets.QMessageBox.StandardButtons(QtWidgets.QMessageBox.Yes |
|
QtWidgets.QMessageBox.StandardButtons(QtWidgets.QMessageBox.Yes |
|
||||||
QtWidgets.QMessageBox.No),
|
QtWidgets.QMessageBox.No),
|
||||||
QtWidgets.QMessageBox.No)
|
QtWidgets.QMessageBox.No)
|
||||||
|
@ -559,7 +562,7 @@ class AdvancedTab(SettingsTab):
|
||||||
"""
|
"""
|
||||||
self.data_directory_path_edit.path = AppLocation.get_data_path()
|
self.data_directory_path_edit.path = AppLocation.get_data_path()
|
||||||
self.data_directory_copy_check_box.setChecked(False)
|
self.data_directory_copy_check_box.setChecked(False)
|
||||||
self.main_window.set_new_data_path(None)
|
self.main_window.new_data_path = None
|
||||||
self.main_window.set_copy_data(False)
|
self.main_window.set_copy_data(False)
|
||||||
self.data_directory_copy_check_box.hide()
|
self.data_directory_copy_check_box.hide()
|
||||||
self.data_directory_cancel_button.hide()
|
self.data_directory_cancel_button.hide()
|
||||||
|
|
|
@ -71,7 +71,7 @@ except ImportError:
|
||||||
VLC_VERSION = '-'
|
VLC_VERSION = '-'
|
||||||
|
|
||||||
from openlp.core.common import RegistryProperties, Settings, UiStrings, is_linux, translate
|
from openlp.core.common import RegistryProperties, Settings, UiStrings, is_linux, translate
|
||||||
from openlp.core.common.versionchecker import get_application_version
|
from openlp.core.version import get_version
|
||||||
from openlp.core.ui.lib.filedialog import FileDialog
|
from openlp.core.ui.lib.filedialog import FileDialog
|
||||||
|
|
||||||
from .exceptiondialog import Ui_ExceptionDialog
|
from .exceptiondialog import Ui_ExceptionDialog
|
||||||
|
@ -110,7 +110,7 @@ class ExceptionForm(QtWidgets.QDialog, Ui_ExceptionDialog, RegistryProperties):
|
||||||
"""
|
"""
|
||||||
Create an exception report.
|
Create an exception report.
|
||||||
"""
|
"""
|
||||||
openlp_version = get_application_version()
|
openlp_version = get_version()
|
||||||
description = self.description_text_edit.toPlainText()
|
description = self.description_text_edit.toPlainText()
|
||||||
traceback = self.exception_text_edit.toPlainText()
|
traceback = self.exception_text_edit.toPlainText()
|
||||||
system = translate('OpenLP.ExceptionForm', 'Platform: {platform}\n').format(platform=platform.platform())
|
system = translate('OpenLP.ExceptionForm', 'Platform: {platform}\n').format(platform=platform.platform())
|
||||||
|
@ -149,21 +149,11 @@ class ExceptionForm(QtWidgets.QDialog, Ui_ExceptionDialog, RegistryProperties):
|
||||||
opts = self._create_report()
|
opts = self._create_report()
|
||||||
report_text = self.report_text.format(version=opts['version'], description=opts['description'],
|
report_text = self.report_text.format(version=opts['version'], description=opts['description'],
|
||||||
traceback=opts['traceback'], libs=opts['libs'], system=opts['system'])
|
traceback=opts['traceback'], libs=opts['libs'], system=opts['system'])
|
||||||
filename = str(file_path)
|
|
||||||
try:
|
try:
|
||||||
report_file = open(filename, 'w')
|
with file_path.open('w') as report_file:
|
||||||
try:
|
|
||||||
report_file.write(report_text)
|
report_file.write(report_text)
|
||||||
except UnicodeError:
|
|
||||||
report_file.close()
|
|
||||||
report_file = open(filename, 'wb')
|
|
||||||
report_file.write(report_text.encode('utf-8'))
|
|
||||||
finally:
|
|
||||||
report_file.close()
|
|
||||||
except IOError:
|
except IOError:
|
||||||
log.exception('Failed to write crash report')
|
log.exception('Failed to write crash report')
|
||||||
finally:
|
|
||||||
report_file.close()
|
|
||||||
|
|
||||||
def on_send_report_button_clicked(self):
|
def on_send_report_button_clicked(self):
|
||||||
"""
|
"""
|
||||||
|
@ -219,7 +209,7 @@ class ExceptionForm(QtWidgets.QDialog, Ui_ExceptionDialog, RegistryProperties):
|
||||||
translate('ImagePlugin.ExceptionDialog', 'Select Attachment'),
|
translate('ImagePlugin.ExceptionDialog', 'Select Attachment'),
|
||||||
Settings().value(self.settings_section + '/last directory'),
|
Settings().value(self.settings_section + '/last directory'),
|
||||||
'{text} (*)'.format(text=UiStrings().AllFiles))
|
'{text} (*)'.format(text=UiStrings().AllFiles))
|
||||||
log.info('New file {file}'.format(file=file_path))
|
log.info('New files {file_path}'.format(file_path=file_path))
|
||||||
if file_path:
|
if file_path:
|
||||||
self.file_attachment = str(file_path)
|
self.file_attachment = str(file_path)
|
||||||
|
|
||||||
|
|
|
@ -181,22 +181,16 @@ class FirstTimeForm(QtWidgets.QWizard, UiFirstTimeWizard, RegistryProperties):
|
||||||
self.application.process_events()
|
self.application.process_events()
|
||||||
try:
|
try:
|
||||||
web_config = get_web_page('{host}{name}'.format(host=self.web, name='download.cfg'),
|
web_config = get_web_page('{host}{name}'.format(host=self.web, name='download.cfg'),
|
||||||
header=('User-Agent', user_agent))
|
headers={'User-Agent': user_agent})
|
||||||
except (urllib.error.URLError, ConnectionError) as err:
|
except ConnectionError:
|
||||||
msg = QtWidgets.QMessageBox()
|
QtWidgets.QMessageBox.critical(self, translate('OpenLP.FirstTimeWizard', 'Network Error'),
|
||||||
title = translate('OpenLP.FirstTimeWizard', 'Network Error')
|
translate('OpenLP.FirstTimeWizard', 'There was a network error attempting '
|
||||||
msg.setText('{title} {error}'.format(title=title,
|
'to connect to retrieve initial configuration information'),
|
||||||
error=err.code if hasattr(err, 'code') else ''))
|
QtWidgets.QMessageBox.Ok)
|
||||||
msg.setInformativeText(translate('OpenLP.FirstTimeWizard',
|
|
||||||
'There was a network error attempting to '
|
|
||||||
'connect to retrieve initial configuration information'))
|
|
||||||
msg.setStandardButtons(msg.Ok)
|
|
||||||
ans = msg.exec()
|
|
||||||
web_config = False
|
web_config = False
|
||||||
if web_config:
|
if web_config:
|
||||||
files = web_config.read()
|
|
||||||
try:
|
try:
|
||||||
self.config.read_string(files.decode())
|
self.config.read_string(web_config)
|
||||||
self.web = self.config.get('general', 'base url')
|
self.web = self.config.get('general', 'base url')
|
||||||
self.songs_url = self.web + self.config.get('songs', 'directory') + '/'
|
self.songs_url = self.web + self.config.get('songs', 'directory') + '/'
|
||||||
self.bibles_url = self.web + self.config.get('bibles', 'directory') + '/'
|
self.bibles_url = self.web + self.config.get('bibles', 'directory') + '/'
|
||||||
|
@ -563,7 +557,7 @@ class FirstTimeForm(QtWidgets.QWizard, UiFirstTimeWizard, RegistryProperties):
|
||||||
filename, sha256 = item.data(QtCore.Qt.UserRole)
|
filename, sha256 = item.data(QtCore.Qt.UserRole)
|
||||||
self._increment_progress_bar(self.downloading.format(name=filename), 0)
|
self._increment_progress_bar(self.downloading.format(name=filename), 0)
|
||||||
self.previous_size = 0
|
self.previous_size = 0
|
||||||
destination = os.path.join(songs_destination, str(filename))
|
destination = Path(songs_destination, str(filename))
|
||||||
if not url_get_file(self, '{path}{name}'.format(path=self.songs_url, name=filename),
|
if not url_get_file(self, '{path}{name}'.format(path=self.songs_url, name=filename),
|
||||||
destination, sha256):
|
destination, sha256):
|
||||||
missed_files.append('Song: {name}'.format(name=filename))
|
missed_files.append('Song: {name}'.format(name=filename))
|
||||||
|
@ -576,7 +570,7 @@ class FirstTimeForm(QtWidgets.QWizard, UiFirstTimeWizard, RegistryProperties):
|
||||||
self._increment_progress_bar(self.downloading.format(name=bible), 0)
|
self._increment_progress_bar(self.downloading.format(name=bible), 0)
|
||||||
self.previous_size = 0
|
self.previous_size = 0
|
||||||
if not url_get_file(self, '{path}{name}'.format(path=self.bibles_url, name=bible),
|
if not url_get_file(self, '{path}{name}'.format(path=self.bibles_url, name=bible),
|
||||||
os.path.join(bibles_destination, bible),
|
Path(bibles_destination, bible),
|
||||||
sha256):
|
sha256):
|
||||||
missed_files.append('Bible: {name}'.format(name=bible))
|
missed_files.append('Bible: {name}'.format(name=bible))
|
||||||
bibles_iterator += 1
|
bibles_iterator += 1
|
||||||
|
@ -588,7 +582,7 @@ class FirstTimeForm(QtWidgets.QWizard, UiFirstTimeWizard, RegistryProperties):
|
||||||
self._increment_progress_bar(self.downloading.format(name=theme), 0)
|
self._increment_progress_bar(self.downloading.format(name=theme), 0)
|
||||||
self.previous_size = 0
|
self.previous_size = 0
|
||||||
if not url_get_file(self, '{path}{name}'.format(path=self.themes_url, name=theme),
|
if not url_get_file(self, '{path}{name}'.format(path=self.themes_url, name=theme),
|
||||||
os.path.join(themes_destination, theme),
|
Path(themes_destination, theme),
|
||||||
sha256):
|
sha256):
|
||||||
missed_files.append('Theme: {name}'.format(name=theme))
|
missed_files.append('Theme: {name}'.format(name=theme))
|
||||||
if missed_files:
|
if missed_files:
|
||||||
|
|
|
@ -163,7 +163,6 @@ class GeneralTab(SettingsTab):
|
||||||
self.startup_layout.addWidget(self.show_splash_check_box)
|
self.startup_layout.addWidget(self.show_splash_check_box)
|
||||||
self.check_for_updates_check_box = QtWidgets.QCheckBox(self.startup_group_box)
|
self.check_for_updates_check_box = QtWidgets.QCheckBox(self.startup_group_box)
|
||||||
self.check_for_updates_check_box.setObjectName('check_for_updates_check_box')
|
self.check_for_updates_check_box.setObjectName('check_for_updates_check_box')
|
||||||
self.check_for_updates_check_box.setVisible(False)
|
|
||||||
self.startup_layout.addWidget(self.check_for_updates_check_box)
|
self.startup_layout.addWidget(self.check_for_updates_check_box)
|
||||||
self.right_layout.addWidget(self.startup_group_box)
|
self.right_layout.addWidget(self.startup_group_box)
|
||||||
# Logo
|
# Logo
|
||||||
|
|
|
@ -22,8 +22,7 @@
|
||||||
""" Patch the QFileDialog so it accepts and returns Path objects"""
|
""" Patch the QFileDialog so it accepts and returns Path objects"""
|
||||||
from PyQt5 import QtWidgets
|
from PyQt5 import QtWidgets
|
||||||
|
|
||||||
from openlp.core.common.path import Path, path_to_str, str_to_path
|
from openlp.core.common.path import Path, path_to_str, replace_params, str_to_path
|
||||||
from openlp.core.lib import replace_params
|
|
||||||
|
|
||||||
|
|
||||||
class FileDialog(QtWidgets.QFileDialog):
|
class FileDialog(QtWidgets.QFileDialog):
|
||||||
|
@ -32,11 +31,11 @@ class FileDialog(QtWidgets.QFileDialog):
|
||||||
"""
|
"""
|
||||||
Wraps `getExistingDirectory` so that it can be called with, and return Path objects
|
Wraps `getExistingDirectory` so that it can be called with, and return Path objects
|
||||||
|
|
||||||
:type parent: QtWidgets.QWidget or None
|
:type parent: QtWidgets.QWidget | None
|
||||||
:type caption: str
|
:type caption: str
|
||||||
:type directory: openlp.core.common.path.Path
|
:type directory: openlp.core.common.path.Path
|
||||||
:type options: QtWidgets.QFileDialog.Options
|
:type options: QtWidgets.QFileDialog.Options
|
||||||
:rtype: tuple[Path, str]
|
:rtype: tuple[openlp.core.common.path.Path, str]
|
||||||
"""
|
"""
|
||||||
args, kwargs = replace_params(args, kwargs, ((2, 'directory', path_to_str),))
|
args, kwargs = replace_params(args, kwargs, ((2, 'directory', path_to_str),))
|
||||||
|
|
||||||
|
@ -51,13 +50,13 @@ class FileDialog(QtWidgets.QFileDialog):
|
||||||
"""
|
"""
|
||||||
Wraps `getOpenFileName` so that it can be called with, and return Path objects
|
Wraps `getOpenFileName` so that it can be called with, and return Path objects
|
||||||
|
|
||||||
:type parent: QtWidgets.QWidget or None
|
:type parent: QtWidgets.QWidget | None
|
||||||
:type caption: str
|
:type caption: str
|
||||||
:type directory: openlp.core.common.path.Path
|
:type directory: openlp.core.common.path.Path
|
||||||
:type filter: str
|
:type filter: str
|
||||||
:type initialFilter: str
|
:type initialFilter: str
|
||||||
:type options: QtWidgets.QFileDialog.Options
|
:type options: QtWidgets.QFileDialog.Options
|
||||||
:rtype: tuple[Path, str]
|
:rtype: tuple[openlp.core.common.path.Path, str]
|
||||||
"""
|
"""
|
||||||
args, kwargs = replace_params(args, kwargs, ((2, 'directory', path_to_str),))
|
args, kwargs = replace_params(args, kwargs, ((2, 'directory', path_to_str),))
|
||||||
|
|
||||||
|
@ -72,13 +71,13 @@ class FileDialog(QtWidgets.QFileDialog):
|
||||||
"""
|
"""
|
||||||
Wraps `getOpenFileNames` so that it can be called with, and return Path objects
|
Wraps `getOpenFileNames` so that it can be called with, and return Path objects
|
||||||
|
|
||||||
:type parent: QtWidgets.QWidget or None
|
:type parent: QtWidgets.QWidget | None
|
||||||
:type caption: str
|
:type caption: str
|
||||||
:type directory: openlp.core.common.path.Path
|
:type directory: openlp.core.common.path.Path
|
||||||
:type filter: str
|
:type filter: str
|
||||||
:type initialFilter: str
|
:type initialFilter: str
|
||||||
:type options: QtWidgets.QFileDialog.Options
|
:type options: QtWidgets.QFileDialog.Options
|
||||||
:rtype: tuple[list[Path], str]
|
:rtype: tuple[list[openlp.core.common.path.Path], str]
|
||||||
"""
|
"""
|
||||||
args, kwargs = replace_params(args, kwargs, ((2, 'directory', path_to_str),))
|
args, kwargs = replace_params(args, kwargs, ((2, 'directory', path_to_str),))
|
||||||
|
|
||||||
|
@ -94,13 +93,13 @@ class FileDialog(QtWidgets.QFileDialog):
|
||||||
"""
|
"""
|
||||||
Wraps `getSaveFileName` so that it can be called with, and return Path objects
|
Wraps `getSaveFileName` so that it can be called with, and return Path objects
|
||||||
|
|
||||||
:type parent: QtWidgets.QWidget or None
|
:type parent: QtWidgets.QWidget | None
|
||||||
:type caption: str
|
:type caption: str
|
||||||
:type directory: openlp.core.common.path.Path
|
:type directory: openlp.core.common.path.Path
|
||||||
:type filter: str
|
:type filter: str
|
||||||
:type initialFilter: str
|
:type initialFilter: str
|
||||||
:type options: QtWidgets.QFileDialog.Options
|
:type options: QtWidgets.QFileDialog.Options
|
||||||
:rtype: tuple[Path or None, str]
|
:rtype: tuple[openlp.core.common.path.Path | None, str]
|
||||||
"""
|
"""
|
||||||
args, kwargs = replace_params(args, kwargs, ((2, 'directory', path_to_str),))
|
args, kwargs = replace_params(args, kwargs, ((2, 'directory', path_to_str),))
|
||||||
|
|
||||||
|
|
|
@ -310,7 +310,7 @@ class OpenLPWizard(QtWidgets.QWizard, RegistryProperties):
|
||||||
"""
|
"""
|
||||||
folder_path = FileDialog.getExistingDirectory(
|
folder_path = FileDialog.getExistingDirectory(
|
||||||
self, title, Settings().value(self.plugin.settings_section + '/' + setting_name),
|
self, title, Settings().value(self.plugin.settings_section + '/' + setting_name),
|
||||||
QtWidgets.QFileDialog.ShowDirsOnly)
|
FileDialog.ShowDirsOnly)
|
||||||
if folder_path:
|
if folder_path:
|
||||||
editbox.setText(str(folder_path))
|
editbox.setText(str(folder_path))
|
||||||
Settings().setValue(self.plugin.settings_section + '/' + setting_name, folder_path)
|
Settings().setValue(self.plugin.settings_section + '/' + setting_name, folder_path)
|
||||||
|
|
|
@ -346,7 +346,7 @@ class MainDisplay(OpenLPMixin, Display, RegistryProperties):
|
||||||
if not hasattr(self, 'service_item'):
|
if not hasattr(self, 'service_item'):
|
||||||
return False
|
return False
|
||||||
self.override['image'] = path
|
self.override['image'] = path
|
||||||
self.override['theme'] = self.service_item.theme_data.background_filename
|
self.override['theme'] = path_to_str(self.service_item.theme_data.background_filename)
|
||||||
self.image(path)
|
self.image(path)
|
||||||
# Update the preview frame.
|
# Update the preview frame.
|
||||||
if self.is_live:
|
if self.is_live:
|
||||||
|
@ -454,7 +454,7 @@ class MainDisplay(OpenLPMixin, Display, RegistryProperties):
|
||||||
Registry().execute('video_background_replaced')
|
Registry().execute('video_background_replaced')
|
||||||
self.override = {}
|
self.override = {}
|
||||||
# We have a different theme.
|
# We have a different theme.
|
||||||
elif self.override['theme'] != service_item.theme_data.background_filename:
|
elif self.override['theme'] != path_to_str(service_item.theme_data.background_filename):
|
||||||
Registry().execute('live_theme_changed')
|
Registry().execute('live_theme_changed')
|
||||||
self.override = {}
|
self.override = {}
|
||||||
else:
|
else:
|
||||||
|
@ -466,7 +466,7 @@ class MainDisplay(OpenLPMixin, Display, RegistryProperties):
|
||||||
if self.service_item.theme_data.background_type == 'image':
|
if self.service_item.theme_data.background_type == 'image':
|
||||||
if self.service_item.theme_data.background_filename:
|
if self.service_item.theme_data.background_filename:
|
||||||
self.service_item.bg_image_bytes = self.image_manager.get_image_bytes(
|
self.service_item.bg_image_bytes = self.image_manager.get_image_bytes(
|
||||||
self.service_item.theme_data.background_filename, ImageSource.Theme)
|
path_to_str(self.service_item.theme_data.background_filename), ImageSource.Theme)
|
||||||
if image_path:
|
if image_path:
|
||||||
image_bytes = self.image_manager.get_image_bytes(image_path, ImageSource.ImagePlugin)
|
image_bytes = self.image_manager.get_image_bytes(image_path, ImageSource.ImagePlugin)
|
||||||
created_html = build_html(self.service_item, self.screen, self.is_live, background, image_bytes,
|
created_html = build_html(self.service_item, self.screen, self.is_live, background, image_bytes,
|
||||||
|
@ -488,7 +488,7 @@ class MainDisplay(OpenLPMixin, Display, RegistryProperties):
|
||||||
path = os.path.join(str(AppLocation.get_section_data_path('themes')),
|
path = os.path.join(str(AppLocation.get_section_data_path('themes')),
|
||||||
self.service_item.theme_data.theme_name)
|
self.service_item.theme_data.theme_name)
|
||||||
service_item.add_from_command(path,
|
service_item.add_from_command(path,
|
||||||
self.service_item.theme_data.background_filename,
|
path_to_str(self.service_item.theme_data.background_filename),
|
||||||
':/media/slidecontroller_multimedia.png')
|
':/media/slidecontroller_multimedia.png')
|
||||||
self.media_controller.video(DisplayControllerType.Live, service_item, video_behind_text=True)
|
self.media_controller.video(DisplayControllerType.Live, service_item, video_behind_text=True)
|
||||||
self._hide_mouse()
|
self._hide_mouse()
|
||||||
|
|
|
@ -39,8 +39,7 @@ from openlp.core.api.http import server
|
||||||
from openlp.core.common import Registry, RegistryProperties, AppLocation, LanguageManager, Settings, UiStrings, \
|
from openlp.core.common import Registry, RegistryProperties, AppLocation, LanguageManager, Settings, UiStrings, \
|
||||||
check_directory_exists, translate, is_win, is_macosx, add_actions
|
check_directory_exists, translate, is_win, is_macosx, add_actions
|
||||||
from openlp.core.common.actions import ActionList, CategoryOrder
|
from openlp.core.common.actions import ActionList, CategoryOrder
|
||||||
from openlp.core.common.path import Path, path_to_str, str_to_path
|
from openlp.core.common.path import Path, copyfile, path_to_str, str_to_path
|
||||||
from openlp.core.common.versionchecker import get_application_version
|
|
||||||
from openlp.core.lib import Renderer, PluginManager, ImageManager, PluginStatus, ScreenList, build_icon
|
from openlp.core.lib import Renderer, PluginManager, ImageManager, PluginStatus, ScreenList, build_icon
|
||||||
from openlp.core.lib.ui import create_action
|
from openlp.core.lib.ui import create_action
|
||||||
from openlp.core.ui import AboutForm, SettingsForm, ServiceManager, ThemeManager, LiveController, PluginForm, \
|
from openlp.core.ui import AboutForm, SettingsForm, ServiceManager, ThemeManager, LiveController, PluginForm, \
|
||||||
|
@ -53,6 +52,7 @@ from openlp.core.ui.projector.manager import ProjectorManager
|
||||||
from openlp.core.ui.lib.dockwidget import OpenLPDockWidget
|
from openlp.core.ui.lib.dockwidget import OpenLPDockWidget
|
||||||
from openlp.core.ui.lib.filedialog import FileDialog
|
from openlp.core.ui.lib.filedialog import FileDialog
|
||||||
from openlp.core.ui.lib.mediadockmanager import MediaDockManager
|
from openlp.core.ui.lib.mediadockmanager import MediaDockManager
|
||||||
|
from openlp.core.version import get_version
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
@ -490,7 +490,6 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
|
||||||
"""
|
"""
|
||||||
The main window.
|
The main window.
|
||||||
"""
|
"""
|
||||||
openlp_version_check = QtCore.pyqtSignal(QtCore.QVariant)
|
|
||||||
log.info('MainWindow loaded')
|
log.info('MainWindow loaded')
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
@ -499,6 +498,8 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
|
||||||
"""
|
"""
|
||||||
super(MainWindow, self).__init__()
|
super(MainWindow, self).__init__()
|
||||||
Registry().register('main_window', self)
|
Registry().register('main_window', self)
|
||||||
|
self.version_thread = None
|
||||||
|
self.version_worker = None
|
||||||
self.clipboard = self.application.clipboard()
|
self.clipboard = self.application.clipboard()
|
||||||
self.arguments = ''.join(self.application.args)
|
self.arguments = ''.join(self.application.args)
|
||||||
# Set up settings sections for the main application (not for use by plugins).
|
# Set up settings sections for the main application (not for use by plugins).
|
||||||
|
@ -564,7 +565,6 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
|
||||||
self.application.set_busy_cursor()
|
self.application.set_busy_cursor()
|
||||||
# Simple message boxes
|
# Simple message boxes
|
||||||
Registry().register_function('theme_update_global', self.default_theme_changed)
|
Registry().register_function('theme_update_global', self.default_theme_changed)
|
||||||
self.openlp_version_check.connect(self.version_notice)
|
|
||||||
Registry().register_function('config_screen_changed', self.screen_changed)
|
Registry().register_function('config_screen_changed', self.screen_changed)
|
||||||
Registry().register_function('bootstrap_post_set_up', self.bootstrap_post_set_up)
|
Registry().register_function('bootstrap_post_set_up', self.bootstrap_post_set_up)
|
||||||
# Reset the cursor
|
# Reset the cursor
|
||||||
|
@ -609,7 +609,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
|
||||||
if widget:
|
if widget:
|
||||||
widget.on_focus()
|
widget.on_focus()
|
||||||
|
|
||||||
def version_notice(self, version):
|
def on_new_version(self, version):
|
||||||
"""
|
"""
|
||||||
Notifies the user that a newer version of OpenLP is available.
|
Notifies the user that a newer version of OpenLP is available.
|
||||||
Triggered by delay thread and cannot display popup.
|
Triggered by delay thread and cannot display popup.
|
||||||
|
@ -619,7 +619,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
|
||||||
log.debug('version_notice')
|
log.debug('version_notice')
|
||||||
version_text = translate('OpenLP.MainWindow', 'Version {new} of OpenLP is now available for download (you are '
|
version_text = translate('OpenLP.MainWindow', 'Version {new} of OpenLP is now available for download (you are '
|
||||||
'currently running version {current}). \n\nYou can download the latest version from '
|
'currently running version {current}). \n\nYou can download the latest version from '
|
||||||
'http://openlp.org/.').format(new=version, current=get_application_version()[u'full'])
|
'http://openlp.org/.').format(new=version, current=get_version()[u'full'])
|
||||||
QtWidgets.QMessageBox.question(self, translate('OpenLP.MainWindow', 'OpenLP Version Updated'), version_text)
|
QtWidgets.QMessageBox.question(self, translate('OpenLP.MainWindow', 'OpenLP Version Updated'), version_text)
|
||||||
|
|
||||||
def show(self):
|
def show(self):
|
||||||
|
@ -850,12 +850,12 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
|
||||||
QtWidgets.QMessageBox.No)
|
QtWidgets.QMessageBox.No)
|
||||||
if answer == QtWidgets.QMessageBox.No:
|
if answer == QtWidgets.QMessageBox.No:
|
||||||
return
|
return
|
||||||
import_file_name, filter_used = QtWidgets.QFileDialog.getOpenFileName(
|
import_file_path, filter_used = FileDialog.getOpenFileName(
|
||||||
self,
|
self,
|
||||||
translate('OpenLP.MainWindow', 'Import settings'),
|
translate('OpenLP.MainWindow', 'Import settings'),
|
||||||
'',
|
None,
|
||||||
translate('OpenLP.MainWindow', 'OpenLP Settings (*.conf)'))
|
translate('OpenLP.MainWindow', 'OpenLP Settings (*.conf)'))
|
||||||
if not import_file_name:
|
if import_file_path is None:
|
||||||
return
|
return
|
||||||
setting_sections = []
|
setting_sections = []
|
||||||
# Add main sections.
|
# Add main sections.
|
||||||
|
@ -873,12 +873,12 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
|
||||||
# Add plugin sections.
|
# Add plugin sections.
|
||||||
setting_sections.extend([plugin.name for plugin in self.plugin_manager.plugins])
|
setting_sections.extend([plugin.name for plugin in self.plugin_manager.plugins])
|
||||||
# Copy the settings file to the tmp dir, because we do not want to change the original one.
|
# Copy the settings file to the tmp dir, because we do not want to change the original one.
|
||||||
temp_directory = os.path.join(str(gettempdir()), 'openlp')
|
temp_dir_path = Path(gettempdir(), 'openlp')
|
||||||
check_directory_exists(Path(temp_directory))
|
check_directory_exists(temp_dir_path)
|
||||||
temp_config = os.path.join(temp_directory, os.path.basename(import_file_name))
|
temp_config_path = temp_dir_path / import_file_path.name
|
||||||
shutil.copyfile(import_file_name, temp_config)
|
copyfile(import_file_path, temp_config_path)
|
||||||
settings = Settings()
|
settings = Settings()
|
||||||
import_settings = Settings(temp_config, Settings.IniFormat)
|
import_settings = Settings(str(temp_config_path), Settings.IniFormat)
|
||||||
|
|
||||||
log.info('hook upgrade_plugin_settings')
|
log.info('hook upgrade_plugin_settings')
|
||||||
self.plugin_manager.hook_upgrade_plugin_settings(import_settings)
|
self.plugin_manager.hook_upgrade_plugin_settings(import_settings)
|
||||||
|
@ -922,7 +922,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
|
||||||
settings.setValue('{key}'.format(key=section_key), value)
|
settings.setValue('{key}'.format(key=section_key), value)
|
||||||
now = datetime.now()
|
now = datetime.now()
|
||||||
settings.beginGroup(self.header_section)
|
settings.beginGroup(self.header_section)
|
||||||
settings.setValue('file_imported', import_file_name)
|
settings.setValue('file_imported', import_file_path)
|
||||||
settings.setValue('file_date_imported', now.strftime("%Y-%m-%d %H:%M"))
|
settings.setValue('file_date_imported', now.strftime("%Y-%m-%d %H:%M"))
|
||||||
settings.endGroup()
|
settings.endGroup()
|
||||||
settings.sync()
|
settings.sync()
|
||||||
|
@ -1013,6 +1013,25 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
|
||||||
if not self.application.is_event_loop_active:
|
if not self.application.is_event_loop_active:
|
||||||
event.ignore()
|
event.ignore()
|
||||||
return
|
return
|
||||||
|
# Sometimes the version thread hasn't finished, let's wait for it
|
||||||
|
try:
|
||||||
|
if self.version_thread and self.version_thread.isRunning():
|
||||||
|
wait_dialog = QtWidgets.QProgressDialog('Waiting for some things to finish...', '', 0, 0, self)
|
||||||
|
wait_dialog.setWindowModality(QtCore.Qt.WindowModal)
|
||||||
|
wait_dialog.setAutoClose(False)
|
||||||
|
wait_dialog.setCancelButton(None)
|
||||||
|
wait_dialog.show()
|
||||||
|
retry = 0
|
||||||
|
while self.version_thread.isRunning() and retry < 50:
|
||||||
|
self.application.processEvents()
|
||||||
|
self.version_thread.wait(100)
|
||||||
|
retry += 1
|
||||||
|
if self.version_thread.isRunning():
|
||||||
|
self.version_thread.terminate()
|
||||||
|
wait_dialog.close()
|
||||||
|
except RuntimeError:
|
||||||
|
# Ignore the RuntimeError that is thrown when Qt has already deleted the C++ thread object
|
||||||
|
pass
|
||||||
# If we just did a settings import, close without saving changes.
|
# If we just did a settings import, close without saving changes.
|
||||||
if self.settings_imported:
|
if self.settings_imported:
|
||||||
self.clean_up(False)
|
self.clean_up(False)
|
||||||
|
@ -1334,12 +1353,6 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
|
||||||
if self.application:
|
if self.application:
|
||||||
self.application.process_events()
|
self.application.process_events()
|
||||||
|
|
||||||
def set_new_data_path(self, new_data_path):
|
|
||||||
"""
|
|
||||||
Set the new data path
|
|
||||||
"""
|
|
||||||
self.new_data_path = new_data_path
|
|
||||||
|
|
||||||
def set_copy_data(self, copy_data):
|
def set_copy_data(self, copy_data):
|
||||||
"""
|
"""
|
||||||
Set the flag to copy the data
|
Set the flag to copy the data
|
||||||
|
@ -1351,7 +1364,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
|
||||||
Change the data directory.
|
Change the data directory.
|
||||||
"""
|
"""
|
||||||
log.info('Changing data path to {newpath}'.format(newpath=self.new_data_path))
|
log.info('Changing data path to {newpath}'.format(newpath=self.new_data_path))
|
||||||
old_data_path = str(AppLocation.get_data_path())
|
old_data_path = AppLocation.get_data_path()
|
||||||
# Copy OpenLP data to new location if requested.
|
# Copy OpenLP data to new location if requested.
|
||||||
self.application.set_busy_cursor()
|
self.application.set_busy_cursor()
|
||||||
if self.copy_data:
|
if self.copy_data:
|
||||||
|
@ -1360,7 +1373,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
|
||||||
self.show_status_message(
|
self.show_status_message(
|
||||||
translate('OpenLP.MainWindow', 'Copying OpenLP data to new data directory location - {path} '
|
translate('OpenLP.MainWindow', 'Copying OpenLP data to new data directory location - {path} '
|
||||||
'- Please wait for copy to finish').format(path=self.new_data_path))
|
'- Please wait for copy to finish').format(path=self.new_data_path))
|
||||||
dir_util.copy_tree(old_data_path, self.new_data_path)
|
dir_util.copy_tree(str(old_data_path), str(self.new_data_path))
|
||||||
log.info('Copy successful')
|
log.info('Copy successful')
|
||||||
except (IOError, os.error, DistutilsFileError) as why:
|
except (IOError, os.error, DistutilsFileError) as why:
|
||||||
self.application.set_normal_cursor()
|
self.application.set_normal_cursor()
|
||||||
|
@ -1375,9 +1388,9 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
|
||||||
log.info('No data copy requested')
|
log.info('No data copy requested')
|
||||||
# Change the location of data directory in config file.
|
# Change the location of data directory in config file.
|
||||||
settings = QtCore.QSettings()
|
settings = QtCore.QSettings()
|
||||||
settings.setValue('advanced/data path', Path(self.new_data_path))
|
settings.setValue('advanced/data path', self.new_data_path)
|
||||||
# Check if the new data path is our default.
|
# Check if the new data path is our default.
|
||||||
if self.new_data_path == str(AppLocation.get_directory(AppLocation.DataDir)):
|
if self.new_data_path == AppLocation.get_directory(AppLocation.DataDir):
|
||||||
settings.remove('advanced/data path')
|
settings.remove('advanced/data path')
|
||||||
self.application.set_normal_cursor()
|
self.application.set_normal_cursor()
|
||||||
|
|
||||||
|
|
|
@ -38,8 +38,7 @@ from openlp.core.lib.projector.constants import ERROR_MSG, ERROR_STRING, E_AUTHE
|
||||||
E_NETWORK, E_NOT_CONNECTED, E_UNKNOWN_SOCKET_ERROR, STATUS_STRING, S_CONNECTED, S_CONNECTING, S_COOLDOWN, \
|
E_NETWORK, E_NOT_CONNECTED, E_UNKNOWN_SOCKET_ERROR, STATUS_STRING, S_CONNECTED, S_CONNECTING, S_COOLDOWN, \
|
||||||
S_INITIALIZE, S_NOT_CONNECTED, S_OFF, S_ON, S_STANDBY, S_WARMUP
|
S_INITIALIZE, S_NOT_CONNECTED, S_OFF, S_ON, S_STANDBY, S_WARMUP
|
||||||
from openlp.core.lib.projector.db import ProjectorDB
|
from openlp.core.lib.projector.db import ProjectorDB
|
||||||
from openlp.core.lib.projector.pjlink import PJLink
|
from openlp.core.lib.projector.pjlink import PJLink, PJLinkUDP
|
||||||
from openlp.core.lib.projector.pjlink2 import PJLinkUDP
|
|
||||||
from openlp.core.ui.projector.editform import ProjectorEditForm
|
from openlp.core.ui.projector.editform import ProjectorEditForm
|
||||||
from openlp.core.ui.projector.sourceselectform import SourceSelectTabs, SourceSelectSingle
|
from openlp.core.ui.projector.sourceselectform import SourceSelectTabs, SourceSelectSingle
|
||||||
|
|
||||||
|
@ -700,16 +699,9 @@ class ProjectorManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, UiProjecto
|
||||||
:returns: PJLink() instance
|
:returns: PJLink() instance
|
||||||
"""
|
"""
|
||||||
log.debug('_add_projector()')
|
log.debug('_add_projector()')
|
||||||
return PJLink(dbid=projector.id,
|
return PJLink(projector=projector,
|
||||||
ip=projector.ip,
|
|
||||||
port=int(projector.port),
|
|
||||||
name=projector.name,
|
|
||||||
location=projector.location,
|
|
||||||
notes=projector.notes,
|
|
||||||
pin=None if projector.pin == '' else projector.pin,
|
|
||||||
poll_time=self.poll_time,
|
poll_time=self.poll_time,
|
||||||
socket_timeout=self.socket_timeout
|
socket_timeout=self.socket_timeout)
|
||||||
)
|
|
||||||
|
|
||||||
def add_projector(self, projector, start=False):
|
def add_projector(self, projector, start=False):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -366,16 +366,20 @@ class ServiceManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ServiceMa
|
||||||
"""
|
"""
|
||||||
return self._modified
|
return self._modified
|
||||||
|
|
||||||
def set_file_name(self, file_name):
|
def set_file_name(self, file_path):
|
||||||
"""
|
"""
|
||||||
Setter for service file.
|
Setter for service file.
|
||||||
|
|
||||||
:param file_name: The service file name
|
:param openlp.core.common.path.Path file_path: The service file name
|
||||||
|
:rtype: None
|
||||||
"""
|
"""
|
||||||
self._file_name = str(file_name)
|
self._file_name = path_to_str(file_path)
|
||||||
self.main_window.set_service_modified(self.is_modified(), self.short_file_name())
|
self.main_window.set_service_modified(self.is_modified(), self.short_file_name())
|
||||||
Settings().setValue('servicemanager/last file', Path(file_name))
|
Settings().setValue('servicemanager/last file', file_path)
|
||||||
self._save_lite = self._file_name.endswith('.oszl')
|
if file_path and file_path.suffix == '.oszl':
|
||||||
|
self._save_lite = True
|
||||||
|
else:
|
||||||
|
self._save_lite = False
|
||||||
|
|
||||||
def file_name(self):
|
def file_name(self):
|
||||||
"""
|
"""
|
||||||
|
@ -474,7 +478,7 @@ class ServiceManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ServiceMa
|
||||||
"""
|
"""
|
||||||
self.service_manager_list.clear()
|
self.service_manager_list.clear()
|
||||||
self.service_items = []
|
self.service_items = []
|
||||||
self.set_file_name('')
|
self.set_file_name(None)
|
||||||
self.service_id += 1
|
self.service_id += 1
|
||||||
self.set_modified(False)
|
self.set_modified(False)
|
||||||
Settings().setValue('servicemanager/last file', None)
|
Settings().setValue('servicemanager/last file', None)
|
||||||
|
@ -695,27 +699,25 @@ class ServiceManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ServiceMa
|
||||||
default_file_name = format_time(default_pattern, local_time)
|
default_file_name = format_time(default_pattern, local_time)
|
||||||
else:
|
else:
|
||||||
default_file_name = ''
|
default_file_name = ''
|
||||||
directory = path_to_str(Settings().value(self.main_window.service_manager_settings_section + '/last directory'))
|
default_file_path = Path(default_file_name)
|
||||||
path = os.path.join(directory, default_file_name)
|
directory_path = Settings().value(self.main_window.service_manager_settings_section + '/last directory')
|
||||||
|
if directory_path:
|
||||||
|
default_file_path = directory_path / default_file_path
|
||||||
# SaveAs from osz to oszl is not valid as the files will be deleted on exit which is not sensible or usable in
|
# SaveAs from osz to oszl is not valid as the files will be deleted on exit which is not sensible or usable in
|
||||||
# the long term.
|
# the long term.
|
||||||
if self._file_name.endswith('oszl') or self.service_has_all_original_files:
|
if self._file_name.endswith('oszl') or self.service_has_all_original_files:
|
||||||
file_name, filter_used = QtWidgets.QFileDialog.getSaveFileName(
|
file_path, filter_used = FileDialog.getSaveFileName(
|
||||||
self.main_window, UiStrings().SaveService, path,
|
self.main_window, UiStrings().SaveService, default_file_path,
|
||||||
translate('OpenLP.ServiceManager',
|
translate('OpenLP.ServiceManager',
|
||||||
'OpenLP Service Files (*.osz);; OpenLP Service Files - lite (*.oszl)'))
|
'OpenLP Service Files (*.osz);; OpenLP Service Files - lite (*.oszl)'))
|
||||||
else:
|
else:
|
||||||
file_name, filter_used = QtWidgets.QFileDialog.getSaveFileName(
|
file_path, filter_used = FileDialog.getSaveFileName(
|
||||||
self.main_window, UiStrings().SaveService, path,
|
self.main_window, UiStrings().SaveService, file_path,
|
||||||
translate('OpenLP.ServiceManager', 'OpenLP Service Files (*.osz);;'))
|
translate('OpenLP.ServiceManager', 'OpenLP Service Files (*.osz);;'))
|
||||||
if not file_name:
|
if not file_path:
|
||||||
return False
|
return False
|
||||||
if os.path.splitext(file_name)[1] == '':
|
file_path.with_suffix('.osz')
|
||||||
file_name += '.osz'
|
self.set_file_name(file_path)
|
||||||
else:
|
|
||||||
ext = os.path.splitext(file_name)[1]
|
|
||||||
file_name.replace(ext, '.osz')
|
|
||||||
self.set_file_name(file_name)
|
|
||||||
self.decide_save_method()
|
self.decide_save_method()
|
||||||
|
|
||||||
def decide_save_method(self, field=None):
|
def decide_save_method(self, field=None):
|
||||||
|
@ -772,7 +774,7 @@ class ServiceManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ServiceMa
|
||||||
return
|
return
|
||||||
file_to.close()
|
file_to.close()
|
||||||
self.new_file()
|
self.new_file()
|
||||||
self.set_file_name(file_name)
|
self.set_file_name(str_to_path(file_name))
|
||||||
self.main_window.display_progress_bar(len(items))
|
self.main_window.display_progress_bar(len(items))
|
||||||
self.process_service_items(items)
|
self.process_service_items(items)
|
||||||
delete_file(Path(p_file))
|
delete_file(Path(p_file))
|
||||||
|
|
|
@ -28,7 +28,6 @@ import os
|
||||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||||
|
|
||||||
from openlp.core.common import Registry, RegistryProperties, UiStrings, translate, get_images_filter, is_not_image_file
|
from openlp.core.common import Registry, RegistryProperties, UiStrings, translate, get_images_filter, is_not_image_file
|
||||||
from openlp.core.common.path import Path, path_to_str, str_to_path
|
|
||||||
from openlp.core.lib.theme import BackgroundType, BackgroundGradientType
|
from openlp.core.lib.theme import BackgroundType, BackgroundGradientType
|
||||||
from openlp.core.lib.ui import critical_error_message_box
|
from openlp.core.lib.ui import critical_error_message_box
|
||||||
from openlp.core.ui import ThemeLayoutForm
|
from openlp.core.ui import ThemeLayoutForm
|
||||||
|
@ -61,7 +60,7 @@ class ThemeForm(QtWidgets.QWizard, Ui_ThemeWizard, RegistryProperties):
|
||||||
self.setupUi(self)
|
self.setupUi(self)
|
||||||
self.registerFields()
|
self.registerFields()
|
||||||
self.update_theme_allowed = True
|
self.update_theme_allowed = True
|
||||||
self.temp_background_filename = ''
|
self.temp_background_filename = None
|
||||||
self.theme_layout_form = ThemeLayoutForm(self)
|
self.theme_layout_form = ThemeLayoutForm(self)
|
||||||
self.background_combo_box.currentIndexChanged.connect(self.on_background_combo_box_current_index_changed)
|
self.background_combo_box.currentIndexChanged.connect(self.on_background_combo_box_current_index_changed)
|
||||||
self.gradient_combo_box.currentIndexChanged.connect(self.on_gradient_combo_box_current_index_changed)
|
self.gradient_combo_box.currentIndexChanged.connect(self.on_gradient_combo_box_current_index_changed)
|
||||||
|
@ -188,8 +187,7 @@ class ThemeForm(QtWidgets.QWizard, Ui_ThemeWizard, RegistryProperties):
|
||||||
"""
|
"""
|
||||||
background_image = BackgroundType.to_string(BackgroundType.Image)
|
background_image = BackgroundType.to_string(BackgroundType.Image)
|
||||||
if self.page(self.currentId()) == self.background_page and \
|
if self.page(self.currentId()) == self.background_page and \
|
||||||
self.theme.background_type == background_image and \
|
self.theme.background_type == background_image and is_not_image_file(self.theme.background_filename):
|
||||||
is_not_image_file(Path(self.theme.background_filename)):
|
|
||||||
QtWidgets.QMessageBox.critical(self, translate('OpenLP.ThemeWizard', 'Background Image Empty'),
|
QtWidgets.QMessageBox.critical(self, translate('OpenLP.ThemeWizard', 'Background Image Empty'),
|
||||||
translate('OpenLP.ThemeWizard', 'You have not selected a '
|
translate('OpenLP.ThemeWizard', 'You have not selected a '
|
||||||
'background image. Please select one before continuing.'))
|
'background image. Please select one before continuing.'))
|
||||||
|
@ -273,7 +271,7 @@ class ThemeForm(QtWidgets.QWizard, Ui_ThemeWizard, RegistryProperties):
|
||||||
Run the wizard.
|
Run the wizard.
|
||||||
"""
|
"""
|
||||||
log.debug('Editing theme {name}'.format(name=self.theme.theme_name))
|
log.debug('Editing theme {name}'.format(name=self.theme.theme_name))
|
||||||
self.temp_background_filename = ''
|
self.temp_background_filename = None
|
||||||
self.update_theme_allowed = False
|
self.update_theme_allowed = False
|
||||||
self.set_defaults()
|
self.set_defaults()
|
||||||
self.update_theme_allowed = True
|
self.update_theme_allowed = True
|
||||||
|
@ -318,11 +316,11 @@ class ThemeForm(QtWidgets.QWizard, Ui_ThemeWizard, RegistryProperties):
|
||||||
self.setField('background_type', 1)
|
self.setField('background_type', 1)
|
||||||
elif self.theme.background_type == BackgroundType.to_string(BackgroundType.Image):
|
elif self.theme.background_type == BackgroundType.to_string(BackgroundType.Image):
|
||||||
self.image_color_button.color = self.theme.background_border_color
|
self.image_color_button.color = self.theme.background_border_color
|
||||||
self.image_path_edit.path = str_to_path(self.theme.background_filename)
|
self.image_path_edit.path = self.theme.background_filename
|
||||||
self.setField('background_type', 2)
|
self.setField('background_type', 2)
|
||||||
elif self.theme.background_type == BackgroundType.to_string(BackgroundType.Video):
|
elif self.theme.background_type == BackgroundType.to_string(BackgroundType.Video):
|
||||||
self.video_color_button.color = self.theme.background_border_color
|
self.video_color_button.color = self.theme.background_border_color
|
||||||
self.video_path_edit.path = str_to_path(self.theme.background_filename)
|
self.video_path_edit.path = self.theme.background_filename
|
||||||
self.setField('background_type', 4)
|
self.setField('background_type', 4)
|
||||||
elif self.theme.background_type == BackgroundType.to_string(BackgroundType.Transparent):
|
elif self.theme.background_type == BackgroundType.to_string(BackgroundType.Transparent):
|
||||||
self.setField('background_type', 3)
|
self.setField('background_type', 3)
|
||||||
|
@ -402,14 +400,14 @@ class ThemeForm(QtWidgets.QWizard, Ui_ThemeWizard, RegistryProperties):
|
||||||
self.theme.background_type = BackgroundType.to_string(index)
|
self.theme.background_type = BackgroundType.to_string(index)
|
||||||
if self.theme.background_type != BackgroundType.to_string(BackgroundType.Image) and \
|
if self.theme.background_type != BackgroundType.to_string(BackgroundType.Image) and \
|
||||||
self.theme.background_type != BackgroundType.to_string(BackgroundType.Video) and \
|
self.theme.background_type != BackgroundType.to_string(BackgroundType.Video) and \
|
||||||
self.temp_background_filename == '':
|
self.temp_background_filename is None:
|
||||||
self.temp_background_filename = self.theme.background_filename
|
self.temp_background_filename = self.theme.background_filename
|
||||||
self.theme.background_filename = ''
|
self.theme.background_filename = None
|
||||||
if (self.theme.background_type == BackgroundType.to_string(BackgroundType.Image) or
|
if (self.theme.background_type == BackgroundType.to_string(BackgroundType.Image) or
|
||||||
self.theme.background_type != BackgroundType.to_string(BackgroundType.Video)) and \
|
self.theme.background_type != BackgroundType.to_string(BackgroundType.Video)) and \
|
||||||
self.temp_background_filename != '':
|
self.temp_background_filename is not None:
|
||||||
self.theme.background_filename = self.temp_background_filename
|
self.theme.background_filename = self.temp_background_filename
|
||||||
self.temp_background_filename = ''
|
self.temp_background_filename = None
|
||||||
self.set_background_page_values()
|
self.set_background_page_values()
|
||||||
|
|
||||||
def on_gradient_combo_box_current_index_changed(self, index):
|
def on_gradient_combo_box_current_index_changed(self, index):
|
||||||
|
@ -450,18 +448,24 @@ class ThemeForm(QtWidgets.QWizard, Ui_ThemeWizard, RegistryProperties):
|
||||||
"""
|
"""
|
||||||
self.theme.background_end_color = color
|
self.theme.background_end_color = color
|
||||||
|
|
||||||
def on_image_path_edit_path_changed(self, file_path):
|
def on_image_path_edit_path_changed(self, new_path):
|
||||||
"""
|
"""
|
||||||
Background Image button pushed.
|
Handle the `pathEditChanged` signal from image_path_edit
|
||||||
|
|
||||||
|
:param openlp.core.common.path.Path new_path: Path to the new image
|
||||||
|
:rtype: None
|
||||||
"""
|
"""
|
||||||
self.theme.background_filename = path_to_str(file_path)
|
self.theme.background_filename = new_path
|
||||||
self.set_background_page_values()
|
self.set_background_page_values()
|
||||||
|
|
||||||
def on_video_path_edit_path_changed(self, file_path):
|
def on_video_path_edit_path_changed(self, new_path):
|
||||||
"""
|
"""
|
||||||
Background video button pushed.
|
Handle the `pathEditChanged` signal from video_path_edit
|
||||||
|
|
||||||
|
:param openlp.core.common.path.Path new_path: Path to the new video
|
||||||
|
:rtype: None
|
||||||
"""
|
"""
|
||||||
self.theme.background_filename = path_to_str(file_path)
|
self.theme.background_filename = new_path
|
||||||
self.set_background_page_values()
|
self.set_background_page_values()
|
||||||
|
|
||||||
def on_main_color_changed(self, color):
|
def on_main_color_changed(self, color):
|
||||||
|
@ -537,14 +541,14 @@ class ThemeForm(QtWidgets.QWizard, Ui_ThemeWizard, RegistryProperties):
|
||||||
translate('OpenLP.ThemeWizard', 'Theme Name Invalid'),
|
translate('OpenLP.ThemeWizard', 'Theme Name Invalid'),
|
||||||
translate('OpenLP.ThemeWizard', 'Invalid theme name. Please enter one.'))
|
translate('OpenLP.ThemeWizard', 'Invalid theme name. Please enter one.'))
|
||||||
return
|
return
|
||||||
save_from = None
|
source_path = None
|
||||||
save_to = None
|
destination_path = None
|
||||||
if self.theme.background_type == BackgroundType.to_string(BackgroundType.Image) or \
|
if self.theme.background_type == BackgroundType.to_string(BackgroundType.Image) or \
|
||||||
self.theme.background_type == BackgroundType.to_string(BackgroundType.Video):
|
self.theme.background_type == BackgroundType.to_string(BackgroundType.Video):
|
||||||
filename = os.path.split(str(self.theme.background_filename))[1]
|
file_name = self.theme.background_filename.name
|
||||||
save_to = os.path.join(self.path, self.theme.theme_name, filename)
|
destination_path = self.path / self.theme.theme_name / file_name
|
||||||
save_from = self.theme.background_filename
|
source_path = self.theme.background_filename
|
||||||
if not self.edit_mode and not self.theme_manager.check_if_theme_exists(self.theme.theme_name):
|
if not self.edit_mode and not self.theme_manager.check_if_theme_exists(self.theme.theme_name):
|
||||||
return
|
return
|
||||||
self.theme_manager.save_theme(self.theme, save_from, save_to)
|
self.theme_manager.save_theme(self.theme, source_path, destination_path)
|
||||||
return QtWidgets.QDialog.accept(self)
|
return QtWidgets.QDialog.accept(self)
|
||||||
|
|
|
@ -24,14 +24,14 @@ The Theme Manager manages adding, deleteing and modifying of themes.
|
||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
import zipfile
|
import zipfile
|
||||||
import shutil
|
|
||||||
|
|
||||||
from xml.etree.ElementTree import ElementTree, XML
|
from xml.etree.ElementTree import ElementTree, XML
|
||||||
|
|
||||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||||
|
|
||||||
from openlp.core.common import Registry, RegistryProperties, AppLocation, Settings, OpenLPMixin, RegistryMixin, \
|
from openlp.core.common import Registry, RegistryProperties, AppLocation, Settings, OpenLPMixin, RegistryMixin, \
|
||||||
UiStrings, check_directory_exists, translate, is_win, get_filesystem_encoding, delete_file
|
UiStrings, check_directory_exists, translate, delete_file
|
||||||
from openlp.core.common.path import Path, path_to_str, str_to_path
|
from openlp.core.common.languagemanager import get_locale_key
|
||||||
|
from openlp.core.common.path import Path, copyfile, path_to_str, rmtree
|
||||||
from openlp.core.lib import ImageSource, ValidationError, get_text_file_string, build_icon, \
|
from openlp.core.lib import ImageSource, ValidationError, get_text_file_string, build_icon, \
|
||||||
check_item_selected, create_thumb, validate_thumb
|
check_item_selected, create_thumb, validate_thumb
|
||||||
from openlp.core.lib.theme import Theme, BackgroundType
|
from openlp.core.lib.theme import Theme, BackgroundType
|
||||||
|
@ -39,7 +39,6 @@ from openlp.core.lib.ui import critical_error_message_box, create_widget_action
|
||||||
from openlp.core.ui import FileRenameForm, ThemeForm
|
from openlp.core.ui import FileRenameForm, ThemeForm
|
||||||
from openlp.core.ui.lib import OpenLPToolbar
|
from openlp.core.ui.lib import OpenLPToolbar
|
||||||
from openlp.core.ui.lib.filedialog import FileDialog
|
from openlp.core.ui.lib.filedialog import FileDialog
|
||||||
from openlp.core.common.languagemanager import get_locale_key
|
|
||||||
|
|
||||||
|
|
||||||
class Ui_ThemeManager(object):
|
class Ui_ThemeManager(object):
|
||||||
|
@ -135,7 +134,7 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ThemeManage
|
||||||
self.settings_section = 'themes'
|
self.settings_section = 'themes'
|
||||||
# Variables
|
# Variables
|
||||||
self.theme_list = []
|
self.theme_list = []
|
||||||
self.old_background_image = None
|
self.old_background_image_path = None
|
||||||
|
|
||||||
def bootstrap_initialise(self):
|
def bootstrap_initialise(self):
|
||||||
"""
|
"""
|
||||||
|
@ -145,25 +144,41 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ThemeManage
|
||||||
self.global_theme = Settings().value(self.settings_section + '/global theme')
|
self.global_theme = Settings().value(self.settings_section + '/global theme')
|
||||||
self.build_theme_path()
|
self.build_theme_path()
|
||||||
self.load_first_time_themes()
|
self.load_first_time_themes()
|
||||||
|
self.upgrade_themes()
|
||||||
|
|
||||||
def bootstrap_post_set_up(self):
|
def bootstrap_post_set_up(self):
|
||||||
"""
|
"""
|
||||||
process the bootstrap post setup request
|
process the bootstrap post setup request
|
||||||
"""
|
"""
|
||||||
self.theme_form = ThemeForm(self)
|
self.theme_form = ThemeForm(self)
|
||||||
self.theme_form.path = self.path
|
self.theme_form.path = self.theme_path
|
||||||
self.file_rename_form = FileRenameForm()
|
self.file_rename_form = FileRenameForm()
|
||||||
Registry().register_function('theme_update_global', self.change_global_from_tab)
|
Registry().register_function('theme_update_global', self.change_global_from_tab)
|
||||||
self.load_themes()
|
self.load_themes()
|
||||||
|
|
||||||
|
def upgrade_themes(self):
|
||||||
|
"""
|
||||||
|
Upgrade the xml files to json.
|
||||||
|
|
||||||
|
:rtype: None
|
||||||
|
"""
|
||||||
|
xml_file_paths = AppLocation.get_section_data_path('themes').glob('*/*.xml')
|
||||||
|
for xml_file_path in xml_file_paths:
|
||||||
|
theme_data = get_text_file_string(xml_file_path)
|
||||||
|
theme = self._create_theme_from_xml(theme_data, self.theme_path)
|
||||||
|
self._write_theme(theme)
|
||||||
|
xml_file_path.unlink()
|
||||||
|
|
||||||
def build_theme_path(self):
|
def build_theme_path(self):
|
||||||
"""
|
"""
|
||||||
Set up the theme path variables
|
Set up the theme path variables
|
||||||
|
|
||||||
|
:rtype: None
|
||||||
"""
|
"""
|
||||||
self.path = str(AppLocation.get_section_data_path(self.settings_section))
|
self.theme_path = AppLocation.get_section_data_path(self.settings_section)
|
||||||
check_directory_exists(Path(self.path))
|
check_directory_exists(self.theme_path)
|
||||||
self.thumb_path = os.path.join(self.path, 'thumbnails')
|
self.thumb_path = self.theme_path / 'thumbnails'
|
||||||
check_directory_exists(Path(self.thumb_path))
|
check_directory_exists(self.thumb_path)
|
||||||
|
|
||||||
def check_list_state(self, item, field=None):
|
def check_list_state(self, item, field=None):
|
||||||
"""
|
"""
|
||||||
|
@ -298,17 +313,18 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ThemeManage
|
||||||
"""
|
"""
|
||||||
Takes a theme and makes a new copy of it as well as saving it.
|
Takes a theme and makes a new copy of it as well as saving it.
|
||||||
|
|
||||||
:param theme_data: The theme to be used
|
:param Theme theme_data: The theme to be used
|
||||||
:param new_theme_name: The new theme name to save the data to
|
:param str new_theme_name: The new theme name of the theme
|
||||||
|
:rtype: None
|
||||||
"""
|
"""
|
||||||
save_to = None
|
destination_path = None
|
||||||
save_from = None
|
source_path = None
|
||||||
if theme_data.background_type == 'image' or theme_data.background_type == 'video':
|
if theme_data.background_type == 'image' or theme_data.background_type == 'video':
|
||||||
save_to = os.path.join(self.path, new_theme_name, os.path.split(str(theme_data.background_filename))[1])
|
destination_path = self.theme_path / new_theme_name / theme_data.background_filename.name
|
||||||
save_from = theme_data.background_filename
|
source_path = theme_data.background_filename
|
||||||
theme_data.theme_name = new_theme_name
|
theme_data.theme_name = new_theme_name
|
||||||
theme_data.extend_image_filename(self.path)
|
theme_data.extend_image_filename(self.theme_path)
|
||||||
self.save_theme(theme_data, save_from, save_to)
|
self.save_theme(theme_data, source_path, destination_path)
|
||||||
self.load_themes()
|
self.load_themes()
|
||||||
|
|
||||||
def on_edit_theme(self, field=None):
|
def on_edit_theme(self, field=None):
|
||||||
|
@ -322,10 +338,10 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ThemeManage
|
||||||
item = self.theme_list_widget.currentItem()
|
item = self.theme_list_widget.currentItem()
|
||||||
theme = self.get_theme_data(item.data(QtCore.Qt.UserRole))
|
theme = self.get_theme_data(item.data(QtCore.Qt.UserRole))
|
||||||
if theme.background_type == 'image' or theme.background_type == 'video':
|
if theme.background_type == 'image' or theme.background_type == 'video':
|
||||||
self.old_background_image = theme.background_filename
|
self.old_background_image_path = theme.background_filename
|
||||||
self.theme_form.theme = theme
|
self.theme_form.theme = theme
|
||||||
self.theme_form.exec(True)
|
self.theme_form.exec(True)
|
||||||
self.old_background_image = None
|
self.old_background_image_path = None
|
||||||
self.renderer.update_theme(theme.theme_name)
|
self.renderer.update_theme(theme.theme_name)
|
||||||
self.load_themes()
|
self.load_themes()
|
||||||
|
|
||||||
|
@ -355,77 +371,76 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ThemeManage
|
||||||
"""
|
"""
|
||||||
self.theme_list.remove(theme)
|
self.theme_list.remove(theme)
|
||||||
thumb = '{name}.png'.format(name=theme)
|
thumb = '{name}.png'.format(name=theme)
|
||||||
delete_file(Path(self.path, thumb))
|
delete_file(self.theme_path / thumb)
|
||||||
delete_file(Path(self.thumb_path, thumb))
|
delete_file(self.thumb_path / thumb)
|
||||||
try:
|
try:
|
||||||
# Windows is always unicode, so no need to encode filenames
|
rmtree(self.theme_path / theme)
|
||||||
if is_win():
|
except OSError:
|
||||||
shutil.rmtree(os.path.join(self.path, theme))
|
|
||||||
else:
|
|
||||||
encoding = get_filesystem_encoding()
|
|
||||||
shutil.rmtree(os.path.join(self.path, theme).encode(encoding))
|
|
||||||
except OSError as os_error:
|
|
||||||
shutil.Error = os_error
|
|
||||||
self.log_exception('Error deleting theme {name}'.format(name=theme))
|
self.log_exception('Error deleting theme {name}'.format(name=theme))
|
||||||
|
|
||||||
def on_export_theme(self, field=None):
|
def on_export_theme(self, checked=None):
|
||||||
"""
|
"""
|
||||||
Export the theme in a zip file
|
Export the theme to a zip file
|
||||||
:param field:
|
|
||||||
|
:param bool checked: Sent by the QAction.triggered signal. It's not used in this method.
|
||||||
|
:rtype: None
|
||||||
"""
|
"""
|
||||||
item = self.theme_list_widget.currentItem()
|
item = self.theme_list_widget.currentItem()
|
||||||
if item is None:
|
if item is None:
|
||||||
critical_error_message_box(message=translate('OpenLP.ThemeManager', 'You have not selected a theme.'))
|
critical_error_message_box(message=translate('OpenLP.ThemeManager', 'You have not selected a theme.'))
|
||||||
return
|
return
|
||||||
theme = item.data(QtCore.Qt.UserRole)
|
theme_name = item.data(QtCore.Qt.UserRole)
|
||||||
export_path, filter_used = \
|
export_path, filter_used = \
|
||||||
FileDialog.getSaveFileName(self.main_window,
|
FileDialog.getSaveFileName(self.main_window,
|
||||||
translate('OpenLP.ThemeManager', 'Save Theme - ({name})').
|
translate('OpenLP.ThemeManager',
|
||||||
format(name=theme),
|
'Save Theme - ({name})').format(name=theme_name),
|
||||||
Settings().value(self.settings_section + '/last directory export'),
|
Settings().value(self.settings_section + '/last directory export'),
|
||||||
|
translate('OpenLP.ThemeManager', 'OpenLP Themes (*.otz)'),
|
||||||
translate('OpenLP.ThemeManager', 'OpenLP Themes (*.otz)'))
|
translate('OpenLP.ThemeManager', 'OpenLP Themes (*.otz)'))
|
||||||
self.application.set_busy_cursor()
|
self.application.set_busy_cursor()
|
||||||
if export_path:
|
if export_path:
|
||||||
Settings().setValue(self.settings_section + '/last directory export', export_path.parent)
|
Settings().setValue(self.settings_section + '/last directory export', export_path.parent)
|
||||||
if self._export_theme(str(export_path), theme):
|
if self._export_theme(export_path.with_suffix('.otz'), theme_name):
|
||||||
QtWidgets.QMessageBox.information(self,
|
QtWidgets.QMessageBox.information(self,
|
||||||
translate('OpenLP.ThemeManager', 'Theme Exported'),
|
translate('OpenLP.ThemeManager', 'Theme Exported'),
|
||||||
translate('OpenLP.ThemeManager',
|
translate('OpenLP.ThemeManager',
|
||||||
'Your theme has been successfully exported.'))
|
'Your theme has been successfully exported.'))
|
||||||
self.application.set_normal_cursor()
|
self.application.set_normal_cursor()
|
||||||
|
|
||||||
def _export_theme(self, theme_path, theme):
|
def _export_theme(self, theme_path, theme_name):
|
||||||
"""
|
"""
|
||||||
Create the zipfile with the theme contents.
|
Create the zipfile with the theme contents.
|
||||||
:param theme_path: Location where the zip file will be placed
|
|
||||||
:param theme: The name of the theme to be exported
|
:param openlp.core.common.path.Path theme_path: Location where the zip file will be placed
|
||||||
|
:param str theme_name: The name of the theme to be exported
|
||||||
|
:return: The success of creating the zip file
|
||||||
|
:rtype: bool
|
||||||
"""
|
"""
|
||||||
theme_zip = None
|
|
||||||
try:
|
try:
|
||||||
theme_zip = zipfile.ZipFile(theme_path, 'w')
|
with zipfile.ZipFile(str(theme_path), 'w') as theme_zip:
|
||||||
source = os.path.join(self.path, theme)
|
source_path = self.theme_path / theme_name
|
||||||
for files in os.walk(source):
|
for file_path in source_path.iterdir():
|
||||||
for name in files[2]:
|
theme_zip.write(str(file_path), os.path.join(theme_name, file_path.name))
|
||||||
theme_zip.write(os.path.join(source, name), os.path.join(theme, name))
|
|
||||||
theme_zip.close()
|
|
||||||
return True
|
return True
|
||||||
except OSError as ose:
|
except OSError as ose:
|
||||||
self.log_exception('Export Theme Failed')
|
self.log_exception('Export Theme Failed')
|
||||||
critical_error_message_box(translate('OpenLP.ThemeManager', 'Theme Export Failed'),
|
critical_error_message_box(translate('OpenLP.ThemeManager', 'Theme Export Failed'),
|
||||||
translate('OpenLP.ThemeManager', 'The theme export failed because this error '
|
translate('OpenLP.ThemeManager',
|
||||||
'occurred: {err}').format(err=ose.strerror))
|
'The theme_name export failed because this error occurred: {err}')
|
||||||
if theme_zip:
|
.format(err=ose.strerror))
|
||||||
theme_zip.close()
|
if theme_path.exists():
|
||||||
shutil.rmtree(theme_path, True)
|
rmtree(theme_path, True)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def on_import_theme(self, field=None):
|
def on_import_theme(self, checked=None):
|
||||||
"""
|
"""
|
||||||
Opens a file dialog to select the theme file(s) to import before attempting to extract OpenLP themes from
|
Opens a file dialog to select the theme file(s) to import before attempting to extract OpenLP themes from
|
||||||
those files. This process will only load version 2 themes.
|
those files. This process will only load version 2 themes.
|
||||||
:param field:
|
|
||||||
|
:param bool checked: Sent by the QAction.triggered signal. It's not used in this method.
|
||||||
|
:rtype: None
|
||||||
"""
|
"""
|
||||||
file_paths, selected_filter = FileDialog.getOpenFileNames(
|
file_paths, filter_used = FileDialog.getOpenFileNames(
|
||||||
self,
|
self,
|
||||||
translate('OpenLP.ThemeManager', 'Select Theme Import File'),
|
translate('OpenLP.ThemeManager', 'Select Theme Import File'),
|
||||||
Settings().value(self.settings_section + '/last directory import'),
|
Settings().value(self.settings_section + '/last directory import'),
|
||||||
|
@ -435,8 +450,8 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ThemeManage
|
||||||
return
|
return
|
||||||
self.application.set_busy_cursor()
|
self.application.set_busy_cursor()
|
||||||
for file_path in file_paths:
|
for file_path in file_paths:
|
||||||
self.unzip_theme(path_to_str(file_path), self.path)
|
self.unzip_theme(file_path, self.theme_path)
|
||||||
Settings().setValue(self.settings_section + '/last directory import', file_path)
|
Settings().setValue(self.settings_section + '/last directory import', file_path.parent)
|
||||||
self.load_themes()
|
self.load_themes()
|
||||||
self.application.set_normal_cursor()
|
self.application.set_normal_cursor()
|
||||||
|
|
||||||
|
@ -445,17 +460,17 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ThemeManage
|
||||||
Imports any themes on start up and makes sure there is at least one theme
|
Imports any themes on start up and makes sure there is at least one theme
|
||||||
"""
|
"""
|
||||||
self.application.set_busy_cursor()
|
self.application.set_busy_cursor()
|
||||||
files = AppLocation.get_files(self.settings_section, '.otz')
|
theme_paths = AppLocation.get_files(self.settings_section, '.otz')
|
||||||
for theme_file in files:
|
for theme_path in theme_paths:
|
||||||
theme_file = os.path.join(self.path, str(theme_file))
|
theme_path = self.theme_path / theme_path
|
||||||
self.unzip_theme(theme_file, self.path)
|
self.unzip_theme(theme_path, self.theme_path)
|
||||||
delete_file(Path(theme_file))
|
delete_file(theme_path)
|
||||||
files = AppLocation.get_files(self.settings_section, '.png')
|
theme_paths = AppLocation.get_files(self.settings_section, '.png')
|
||||||
# No themes have been found so create one
|
# No themes have been found so create one
|
||||||
if not files:
|
if not theme_paths:
|
||||||
theme = Theme()
|
theme = Theme()
|
||||||
theme.theme_name = UiStrings().Default
|
theme.theme_name = UiStrings().Default
|
||||||
self._write_theme(theme, None, None)
|
self._write_theme(theme)
|
||||||
Settings().setValue(self.settings_section + '/global theme', theme.theme_name)
|
Settings().setValue(self.settings_section + '/global theme', theme.theme_name)
|
||||||
self.application.set_normal_cursor()
|
self.application.set_normal_cursor()
|
||||||
|
|
||||||
|
@ -471,22 +486,21 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ThemeManage
|
||||||
# Sort the themes by its name considering language specific
|
# Sort the themes by its name considering language specific
|
||||||
files.sort(key=lambda file_name: get_locale_key(str(file_name)))
|
files.sort(key=lambda file_name: get_locale_key(str(file_name)))
|
||||||
# now process the file list of png files
|
# now process the file list of png files
|
||||||
for name in files:
|
for file in files:
|
||||||
name = str(name)
|
|
||||||
# check to see file is in theme root directory
|
# check to see file is in theme root directory
|
||||||
theme = os.path.join(self.path, name)
|
theme_path = self.theme_path / file
|
||||||
if os.path.exists(theme):
|
if theme_path.exists():
|
||||||
text_name = os.path.splitext(name)[0]
|
text_name = theme_path.stem
|
||||||
if text_name == self.global_theme:
|
if text_name == self.global_theme:
|
||||||
name = translate('OpenLP.ThemeManager', '{name} (default)').format(name=text_name)
|
name = translate('OpenLP.ThemeManager', '{name} (default)').format(name=text_name)
|
||||||
else:
|
else:
|
||||||
name = text_name
|
name = text_name
|
||||||
thumb = os.path.join(self.thumb_path, '{name}.png'.format(name=text_name))
|
thumb = self.thumb_path / '{name}.png'.format(name=text_name)
|
||||||
item_name = QtWidgets.QListWidgetItem(name)
|
item_name = QtWidgets.QListWidgetItem(name)
|
||||||
if validate_thumb(theme, thumb):
|
if validate_thumb(theme_path, thumb):
|
||||||
icon = build_icon(thumb)
|
icon = build_icon(thumb)
|
||||||
else:
|
else:
|
||||||
icon = create_thumb(theme, thumb)
|
icon = create_thumb(str(theme_path), str(thumb))
|
||||||
item_name.setIcon(icon)
|
item_name.setIcon(icon)
|
||||||
item_name.setData(QtCore.Qt.UserRole, text_name)
|
item_name.setData(QtCore.Qt.UserRole, text_name)
|
||||||
self.theme_list_widget.addItem(item_name)
|
self.theme_list_widget.addItem(item_name)
|
||||||
|
@ -507,27 +521,19 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ThemeManage
|
||||||
|
|
||||||
def get_theme_data(self, theme_name):
|
def get_theme_data(self, theme_name):
|
||||||
"""
|
"""
|
||||||
Returns a theme object from an XML or JSON file
|
Returns a theme object from a JSON file
|
||||||
|
|
||||||
:param theme_name: Name of the theme to load from file
|
:param str theme_name: Name of the theme to load from file
|
||||||
:return: The theme object.
|
:return: The theme object.
|
||||||
|
:rtype: Theme
|
||||||
"""
|
"""
|
||||||
self.log_debug('get theme data for theme {name}'.format(name=theme_name))
|
theme_name = str(theme_name)
|
||||||
theme_file_path = Path(self.path, str(theme_name), '{file_name}.json'.format(file_name=theme_name))
|
theme_file_path = self.theme_path / theme_name / '{file_name}.json'.format(file_name=theme_name)
|
||||||
theme_data = get_text_file_string(theme_file_path)
|
theme_data = get_text_file_string(theme_file_path)
|
||||||
jsn = True
|
|
||||||
if not theme_data:
|
|
||||||
theme_file_path = theme_file_path.with_suffix('.xml')
|
|
||||||
theme_data = get_text_file_string(theme_file_path)
|
|
||||||
jsn = False
|
|
||||||
if not theme_data:
|
if not theme_data:
|
||||||
self.log_debug('No theme data - using default theme')
|
self.log_debug('No theme data - using default theme')
|
||||||
return Theme()
|
return Theme()
|
||||||
else:
|
return self._create_theme_from_json(theme_data, self.theme_path)
|
||||||
if jsn:
|
|
||||||
return self._create_theme_from_json(theme_data, self.path)
|
|
||||||
else:
|
|
||||||
return self._create_theme_from_xml(theme_data, self.path)
|
|
||||||
|
|
||||||
def over_write_message_box(self, theme_name):
|
def over_write_message_box(self, theme_name):
|
||||||
"""
|
"""
|
||||||
|
@ -543,172 +549,148 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ThemeManage
|
||||||
defaultButton=QtWidgets.QMessageBox.No)
|
defaultButton=QtWidgets.QMessageBox.No)
|
||||||
return ret == QtWidgets.QMessageBox.Yes
|
return ret == QtWidgets.QMessageBox.Yes
|
||||||
|
|
||||||
def unzip_theme(self, file_name, directory):
|
def unzip_theme(self, file_path, directory_path):
|
||||||
"""
|
"""
|
||||||
Unzip the theme, remove the preview file if stored. Generate a new preview file. Check the XML theme version
|
Unzip the theme, remove the preview file if stored. Generate a new preview file. Check the XML theme version
|
||||||
and upgrade if necessary.
|
and upgrade if necessary.
|
||||||
:param file_name:
|
:param openlp.core.common.path.Path file_path:
|
||||||
:param directory:
|
:param openlp.core.common.path.Path directory_path:
|
||||||
"""
|
"""
|
||||||
self.log_debug('Unzipping theme {name}'.format(name=file_name))
|
self.log_debug('Unzipping theme {name}'.format(name=file_path))
|
||||||
theme_zip = None
|
|
||||||
out_file = None
|
|
||||||
file_xml = None
|
file_xml = None
|
||||||
abort_import = True
|
abort_import = True
|
||||||
json_theme = False
|
json_theme = False
|
||||||
theme_name = ""
|
theme_name = ""
|
||||||
try:
|
try:
|
||||||
theme_zip = zipfile.ZipFile(file_name)
|
with zipfile.ZipFile(str(file_path)) as theme_zip:
|
||||||
json_file = [name for name in theme_zip.namelist() if os.path.splitext(name)[1].lower() == '.json']
|
json_file = [name for name in theme_zip.namelist() if os.path.splitext(name)[1].lower() == '.json']
|
||||||
if len(json_file) != 1:
|
if len(json_file) != 1:
|
||||||
# TODO: remove XML handling at some point but would need a auto conversion to run first.
|
# TODO: remove XML handling after the 2.6 release.
|
||||||
xml_file = [name for name in theme_zip.namelist() if os.path.splitext(name)[1].lower() == '.xml']
|
xml_file = [name for name in theme_zip.namelist() if os.path.splitext(name)[1].lower() == '.xml']
|
||||||
if len(xml_file) != 1:
|
if len(xml_file) != 1:
|
||||||
self.log_error('Theme contains "{val:d}" theme files'.format(val=len(xml_file)))
|
self.log_error('Theme contains "{val:d}" theme files'.format(val=len(xml_file)))
|
||||||
raise ValidationError
|
raise ValidationError
|
||||||
xml_tree = ElementTree(element=XML(theme_zip.read(xml_file[0]))).getroot()
|
xml_tree = ElementTree(element=XML(theme_zip.read(xml_file[0]))).getroot()
|
||||||
theme_version = xml_tree.get('version', default=None)
|
theme_version = xml_tree.get('version', default=None)
|
||||||
if not theme_version or float(theme_version) < 2.0:
|
if not theme_version or float(theme_version) < 2.0:
|
||||||
self.log_error('Theme version is less than 2.0')
|
self.log_error('Theme version is less than 2.0')
|
||||||
raise ValidationError
|
raise ValidationError
|
||||||
theme_name = xml_tree.find('name').text.strip()
|
theme_name = xml_tree.find('name').text.strip()
|
||||||
else:
|
|
||||||
new_theme = Theme()
|
|
||||||
new_theme.load_theme(theme_zip.read(json_file[0]).decode("utf-8"))
|
|
||||||
theme_name = new_theme.theme_name
|
|
||||||
json_theme = True
|
|
||||||
theme_folder = os.path.join(directory, theme_name)
|
|
||||||
theme_exists = os.path.exists(theme_folder)
|
|
||||||
if theme_exists and not self.over_write_message_box(theme_name):
|
|
||||||
abort_import = True
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
abort_import = False
|
|
||||||
for name in theme_zip.namelist():
|
|
||||||
out_name = name.replace('/', os.path.sep)
|
|
||||||
split_name = out_name.split(os.path.sep)
|
|
||||||
if split_name[-1] == '' or len(split_name) == 1:
|
|
||||||
# is directory or preview file
|
|
||||||
continue
|
|
||||||
full_name = os.path.join(directory, out_name)
|
|
||||||
check_directory_exists(Path(os.path.dirname(full_name)))
|
|
||||||
if os.path.splitext(name)[1].lower() == '.xml' or os.path.splitext(name)[1].lower() == '.json':
|
|
||||||
file_xml = str(theme_zip.read(name), 'utf-8')
|
|
||||||
out_file = open(full_name, 'w', encoding='utf-8')
|
|
||||||
out_file.write(file_xml)
|
|
||||||
else:
|
else:
|
||||||
out_file = open(full_name, 'wb')
|
new_theme = Theme()
|
||||||
out_file.write(theme_zip.read(name))
|
new_theme.load_theme(theme_zip.read(json_file[0]).decode("utf-8"))
|
||||||
out_file.close()
|
theme_name = new_theme.theme_name
|
||||||
|
json_theme = True
|
||||||
|
theme_folder = directory_path / theme_name
|
||||||
|
if theme_folder.exists() and not self.over_write_message_box(theme_name):
|
||||||
|
abort_import = True
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
abort_import = False
|
||||||
|
for zipped_file in theme_zip.namelist():
|
||||||
|
zipped_file_rel_path = Path(zipped_file)
|
||||||
|
split_name = zipped_file_rel_path.parts
|
||||||
|
if split_name[-1] == '' or len(split_name) == 1:
|
||||||
|
# is directory or preview file
|
||||||
|
continue
|
||||||
|
full_name = directory_path / zipped_file_rel_path
|
||||||
|
check_directory_exists(full_name.parent)
|
||||||
|
if zipped_file_rel_path.suffix.lower() == '.xml' or zipped_file_rel_path.suffix.lower() == '.json':
|
||||||
|
file_xml = str(theme_zip.read(zipped_file), 'utf-8')
|
||||||
|
with full_name.open('w', encoding='utf-8') as out_file:
|
||||||
|
out_file.write(file_xml)
|
||||||
|
else:
|
||||||
|
with full_name.open('wb') as out_file:
|
||||||
|
out_file.write(theme_zip.read(zipped_file))
|
||||||
except (IOError, zipfile.BadZipfile):
|
except (IOError, zipfile.BadZipfile):
|
||||||
self.log_exception('Importing theme from zip failed {name}'.format(name=file_name))
|
self.log_exception('Importing theme from zip failed {name}'.format(name=file_path))
|
||||||
raise ValidationError
|
raise ValidationError
|
||||||
except ValidationError:
|
except ValidationError:
|
||||||
critical_error_message_box(translate('OpenLP.ThemeManager', 'Validation Error'),
|
critical_error_message_box(translate('OpenLP.ThemeManager', 'Validation Error'),
|
||||||
translate('OpenLP.ThemeManager', 'File is not a valid theme.'))
|
translate('OpenLP.ThemeManager', 'File is not a valid theme.'))
|
||||||
finally:
|
finally:
|
||||||
# Close the files, to be able to continue creating the theme.
|
|
||||||
if theme_zip:
|
|
||||||
theme_zip.close()
|
|
||||||
if out_file:
|
|
||||||
out_file.close()
|
|
||||||
if not abort_import:
|
if not abort_import:
|
||||||
# As all files are closed, we can create the Theme.
|
# As all files are closed, we can create the Theme.
|
||||||
if file_xml:
|
if file_xml:
|
||||||
if json_theme:
|
if json_theme:
|
||||||
theme = self._create_theme_from_json(file_xml, self.path)
|
theme = self._create_theme_from_json(file_xml, self.theme_path)
|
||||||
else:
|
else:
|
||||||
theme = self._create_theme_from_xml(file_xml, self.path)
|
theme = self._create_theme_from_xml(file_xml, self.theme_path)
|
||||||
self.generate_and_save_image(theme_name, theme)
|
self.generate_and_save_image(theme_name, theme)
|
||||||
# Only show the error message, when IOError was not raised (in
|
|
||||||
# this case the error message has already been shown).
|
|
||||||
elif theme_zip is not None:
|
|
||||||
critical_error_message_box(
|
|
||||||
translate('OpenLP.ThemeManager', 'Validation Error'),
|
|
||||||
translate('OpenLP.ThemeManager', 'File is not a valid theme.'))
|
|
||||||
self.log_error('Theme file does not contain XML data {name}'.format(name=file_name))
|
|
||||||
|
|
||||||
def check_if_theme_exists(self, theme_name):
|
def check_if_theme_exists(self, theme_name):
|
||||||
"""
|
"""
|
||||||
Check if theme already exists and displays error message
|
Check if theme already exists and displays error message
|
||||||
|
|
||||||
:param theme_name: Name of the Theme to test
|
:param str theme_name: Name of the Theme to test
|
||||||
:return: True or False if theme exists
|
:return: True or False if theme exists
|
||||||
|
:rtype: bool
|
||||||
"""
|
"""
|
||||||
theme_dir = os.path.join(self.path, theme_name)
|
if (self.theme_path / theme_name).exists():
|
||||||
if os.path.exists(theme_dir):
|
|
||||||
critical_error_message_box(
|
critical_error_message_box(
|
||||||
translate('OpenLP.ThemeManager', 'Validation Error'),
|
translate('OpenLP.ThemeManager', 'Validation Error'),
|
||||||
translate('OpenLP.ThemeManager', 'A theme with this name already exists.'))
|
translate('OpenLP.ThemeManager', 'A theme with this name already exists.'))
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def save_theme(self, theme, image_from, image_to):
|
def save_theme(self, theme, image_source_path, image_destination_path):
|
||||||
"""
|
"""
|
||||||
Called by theme maintenance Dialog to save the theme and to trigger the reload of the theme list
|
Called by theme maintenance Dialog to save the theme and to trigger the reload of the theme list
|
||||||
|
|
||||||
:param theme: The theme data object.
|
:param Theme theme: The theme data object.
|
||||||
:param image_from: Where the theme image is currently located.
|
:param openlp.core.common.path.Path image_source_path: Where the theme image is currently located.
|
||||||
:param image_to: Where the Theme Image is to be saved to
|
:param openlp.core.common.path.Path image_destination_path: Where the Theme Image is to be saved to
|
||||||
|
:rtype: None
|
||||||
"""
|
"""
|
||||||
self._write_theme(theme, image_from, image_to)
|
self._write_theme(theme, image_source_path, image_destination_path)
|
||||||
if theme.background_type == BackgroundType.to_string(BackgroundType.Image):
|
if theme.background_type == BackgroundType.to_string(BackgroundType.Image):
|
||||||
self.image_manager.update_image_border(theme.background_filename,
|
self.image_manager.update_image_border(path_to_str(theme.background_filename),
|
||||||
ImageSource.Theme,
|
ImageSource.Theme,
|
||||||
QtGui.QColor(theme.background_border_color))
|
QtGui.QColor(theme.background_border_color))
|
||||||
self.image_manager.process_updates()
|
self.image_manager.process_updates()
|
||||||
|
|
||||||
def _write_theme(self, theme, image_from, image_to):
|
def _write_theme(self, theme, image_source_path=None, image_destination_path=None):
|
||||||
"""
|
"""
|
||||||
Writes the theme to the disk and handles the background image if necessary
|
Writes the theme to the disk and handles the background image if necessary
|
||||||
|
|
||||||
:param theme: The theme data object.
|
:param Theme theme: The theme data object.
|
||||||
:param image_from: Where the theme image is currently located.
|
:param openlp.core.common.path.Path image_source_path: Where the theme image is currently located.
|
||||||
:param image_to: Where the Theme Image is to be saved to
|
:param openlp.core.common.path.Path image_destination_path: Where the Theme Image is to be saved to
|
||||||
|
:rtype: None
|
||||||
"""
|
"""
|
||||||
name = theme.theme_name
|
name = theme.theme_name
|
||||||
theme_pretty = theme.export_theme()
|
theme_pretty = theme.export_theme(self.theme_path)
|
||||||
theme_dir = os.path.join(self.path, name)
|
theme_dir = self.theme_path / name
|
||||||
check_directory_exists(Path(theme_dir))
|
check_directory_exists(theme_dir)
|
||||||
theme_file = os.path.join(theme_dir, name + '.json')
|
theme_path = theme_dir / '{file_name}.json'.format(file_name=name)
|
||||||
if self.old_background_image and image_to != self.old_background_image:
|
|
||||||
delete_file(Path(self.old_background_image))
|
|
||||||
out_file = None
|
|
||||||
try:
|
try:
|
||||||
out_file = open(theme_file, 'w', encoding='utf-8')
|
theme_path.write_text(theme_pretty)
|
||||||
out_file.write(theme_pretty)
|
|
||||||
except IOError:
|
except IOError:
|
||||||
self.log_exception('Saving theme to file failed')
|
self.log_exception('Saving theme to file failed')
|
||||||
finally:
|
if image_source_path and image_destination_path:
|
||||||
if out_file:
|
if self.old_background_image_path and image_destination_path != self.old_background_image_path:
|
||||||
out_file.close()
|
delete_file(self.old_background_image_path)
|
||||||
if image_from and os.path.abspath(image_from) != os.path.abspath(image_to):
|
if image_source_path != image_destination_path:
|
||||||
try:
|
try:
|
||||||
# Windows is always unicode, so no need to encode filenames
|
copyfile(image_source_path, image_destination_path)
|
||||||
if is_win():
|
except IOError:
|
||||||
shutil.copyfile(image_from, image_to)
|
self.log_exception('Failed to save theme image')
|
||||||
else:
|
|
||||||
encoding = get_filesystem_encoding()
|
|
||||||
shutil.copyfile(image_from.encode(encoding), image_to.encode(encoding))
|
|
||||||
except IOError as xxx_todo_changeme:
|
|
||||||
shutil.Error = xxx_todo_changeme
|
|
||||||
self.log_exception('Failed to save theme image')
|
|
||||||
self.generate_and_save_image(name, theme)
|
self.generate_and_save_image(name, theme)
|
||||||
|
|
||||||
def generate_and_save_image(self, name, theme):
|
def generate_and_save_image(self, theme_name, theme):
|
||||||
"""
|
"""
|
||||||
Generate and save a preview image
|
Generate and save a preview image
|
||||||
|
|
||||||
:param name: The name of the theme.
|
:param str theme_name: The name of the theme.
|
||||||
:param theme: The theme data object.
|
:param theme: The theme data object.
|
||||||
"""
|
"""
|
||||||
frame = self.generate_image(theme)
|
frame = self.generate_image(theme)
|
||||||
sample_path_name = os.path.join(self.path, name + '.png')
|
sample_path_name = self.theme_path / '{file_name}.png'.format(file_name=theme_name)
|
||||||
if os.path.exists(sample_path_name):
|
if sample_path_name.exists():
|
||||||
os.unlink(sample_path_name)
|
sample_path_name.unlink()
|
||||||
frame.save(sample_path_name, 'png')
|
frame.save(str(sample_path_name), 'png')
|
||||||
thumb = os.path.join(self.thumb_path, '{name}.png'.format(name=name))
|
thumb_path = self.thumb_path / '{name}.png'.format(name=theme_name)
|
||||||
create_thumb(sample_path_name, thumb, False)
|
create_thumb(str(sample_path_name), str(thumb_path), False)
|
||||||
|
|
||||||
def update_preview_images(self):
|
def update_preview_images(self):
|
||||||
"""
|
"""
|
||||||
|
@ -730,39 +712,32 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ThemeManage
|
||||||
"""
|
"""
|
||||||
return self.renderer.generate_preview(theme_data, force_page)
|
return self.renderer.generate_preview(theme_data, force_page)
|
||||||
|
|
||||||
def get_preview_image(self, theme):
|
|
||||||
"""
|
|
||||||
Return an image representing the look of the theme
|
|
||||||
|
|
||||||
:param theme: The theme to return the image for.
|
|
||||||
"""
|
|
||||||
return os.path.join(self.path, theme + '.png')
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _create_theme_from_xml(theme_xml, image_path):
|
def _create_theme_from_xml(theme_xml, image_path):
|
||||||
"""
|
"""
|
||||||
Return a theme object using information parsed from XML
|
Return a theme object using information parsed from XML
|
||||||
|
|
||||||
:param theme_xml: The Theme data object.
|
:param theme_xml: The Theme data object.
|
||||||
:param image_path: Where the theme image is stored
|
:param openlp.core.common.path.Path image_path: Where the theme image is stored
|
||||||
:return: Theme data.
|
:return: Theme data.
|
||||||
|
:rtype: Theme
|
||||||
"""
|
"""
|
||||||
theme = Theme()
|
theme = Theme()
|
||||||
theme.parse(theme_xml)
|
theme.parse(theme_xml)
|
||||||
theme.extend_image_filename(image_path)
|
theme.extend_image_filename(image_path)
|
||||||
return theme
|
return theme
|
||||||
|
|
||||||
@staticmethod
|
def _create_theme_from_json(self, theme_json, image_path):
|
||||||
def _create_theme_from_json(theme_json, image_path):
|
|
||||||
"""
|
"""
|
||||||
Return a theme object using information parsed from JSON
|
Return a theme object using information parsed from JSON
|
||||||
|
|
||||||
:param theme_json: The Theme data object.
|
:param theme_json: The Theme data object.
|
||||||
:param image_path: Where the theme image is stored
|
:param openlp.core.common.path.Path image_path: Where the theme image is stored
|
||||||
:return: Theme data.
|
:return: Theme data.
|
||||||
|
:rtype: Theme
|
||||||
"""
|
"""
|
||||||
theme = Theme()
|
theme = Theme()
|
||||||
theme.load_theme(theme_json)
|
theme.load_theme(theme_json, self.theme_path)
|
||||||
theme.extend_image_filename(image_path)
|
theme.extend_image_filename(image_path)
|
||||||
return theme
|
return theme
|
||||||
|
|
||||||
|
|
|
@ -211,8 +211,8 @@ class ThemesTab(SettingsTab):
|
||||||
"""
|
"""
|
||||||
Utility method to update the global theme preview image.
|
Utility method to update the global theme preview image.
|
||||||
"""
|
"""
|
||||||
image = self.theme_manager.get_preview_image(self.global_theme)
|
image_path = self.theme_manager.theme_path / '{file_name}.png'.format(file_name=self.global_theme)
|
||||||
preview = QtGui.QPixmap(str(image))
|
preview = QtGui.QPixmap(str(image_path))
|
||||||
if not preview.isNull():
|
if not preview.isNull():
|
||||||
preview = preview.scaled(300, 255, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation)
|
preview = preview.scaled(300, 255, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation)
|
||||||
self.default_list_view.setPixmap(preview)
|
self.default_list_view.setPixmap(preview)
|
||||||
|
|
|
@ -20,24 +20,22 @@
|
||||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||||
###############################################################################
|
###############################################################################
|
||||||
"""
|
"""
|
||||||
The :mod:`openlp.core.common` module downloads the version details for OpenLP.
|
The :mod:`openlp.core.version` module downloads the version details for OpenLP.
|
||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import platform
|
import platform
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
import urllib.error
|
from datetime import date
|
||||||
import urllib.parse
|
|
||||||
import urllib.request
|
|
||||||
from datetime import datetime
|
|
||||||
from distutils.version import LooseVersion
|
from distutils.version import LooseVersion
|
||||||
from subprocess import Popen, PIPE
|
from subprocess import Popen, PIPE
|
||||||
|
|
||||||
|
import requests
|
||||||
from PyQt5 import QtCore
|
from PyQt5 import QtCore
|
||||||
|
|
||||||
from openlp.core.common import AppLocation, Registry, Settings
|
from openlp.core.common import AppLocation, Settings
|
||||||
from openlp.core.common.httputils import ping
|
from openlp.core.threading import run_thread
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -46,42 +44,93 @@ CONNECTION_TIMEOUT = 30
|
||||||
CONNECTION_RETRIES = 2
|
CONNECTION_RETRIES = 2
|
||||||
|
|
||||||
|
|
||||||
class VersionThread(QtCore.QThread):
|
class VersionWorker(QtCore.QObject):
|
||||||
"""
|
"""
|
||||||
A special Qt thread class to fetch the version of OpenLP from the website.
|
A worker class to fetch the version of OpenLP from the website. This is run from within a thread so that it
|
||||||
This is threaded so that it doesn't affect the loading time of OpenLP.
|
doesn't affect the loading time of OpenLP.
|
||||||
"""
|
"""
|
||||||
def __init__(self, main_window):
|
new_version = QtCore.pyqtSignal(dict)
|
||||||
"""
|
no_internet = QtCore.pyqtSignal()
|
||||||
Constructor for the thread class.
|
quit = QtCore.pyqtSignal()
|
||||||
|
|
||||||
:param main_window: The main window Object.
|
def __init__(self, last_check_date, current_version):
|
||||||
"""
|
"""
|
||||||
log.debug("VersionThread - Initialise")
|
Constructor for the version check worker.
|
||||||
super(VersionThread, self).__init__(None)
|
|
||||||
self.main_window = main_window
|
|
||||||
|
|
||||||
def run(self):
|
:param string last_check_date: The last day we checked for a new version of OpenLP
|
||||||
"""
|
"""
|
||||||
Run the thread.
|
log.debug('VersionWorker - Initialise')
|
||||||
|
super(VersionWorker, self).__init__(None)
|
||||||
|
self.last_check_date = last_check_date
|
||||||
|
self.current_version = current_version
|
||||||
|
|
||||||
|
def start(self):
|
||||||
"""
|
"""
|
||||||
self.sleep(1)
|
Check the latest version of OpenLP against the version file on the OpenLP site.
|
||||||
log.debug('Version thread - run')
|
|
||||||
found = ping("openlp.io")
|
**Rules around versions and version files:**
|
||||||
Registry().set_flag('internet_present', found)
|
|
||||||
update_check = Settings().value('core/update check')
|
* If a version number has a build (i.e. -bzr1234), then it is a nightly.
|
||||||
if found:
|
* If a version number's minor version is an odd number, it is a development release.
|
||||||
Registry().execute('get_website_version')
|
* If a version number's minor version is an even number, it is a stable release.
|
||||||
if update_check:
|
"""
|
||||||
app_version = get_application_version()
|
log.debug('VersionWorker - Start')
|
||||||
version = check_latest_version(app_version)
|
# I'm not entirely sure why this was here, I'm commenting it out until I hit the same scenario
|
||||||
log.debug("Versions {version1} and {version2} ".format(version1=LooseVersion(str(version)),
|
time.sleep(1)
|
||||||
version2=LooseVersion(str(app_version['full']))))
|
download_url = 'http://www.openlp.org/files/version.txt'
|
||||||
if LooseVersion(str(version)) > LooseVersion(str(app_version['full'])):
|
if self.current_version['build']:
|
||||||
self.main_window.openlp_version_check.emit('{version}'.format(version=version))
|
download_url = 'http://www.openlp.org/files/nightly_version.txt'
|
||||||
|
elif int(self.current_version['version'].split('.')[1]) % 2 != 0:
|
||||||
|
download_url = 'http://www.openlp.org/files/dev_version.txt'
|
||||||
|
headers = {
|
||||||
|
'User-Agent': 'OpenLP/{version} {system}/{release}; '.format(version=self.current_version['full'],
|
||||||
|
system=platform.system(),
|
||||||
|
release=platform.release())
|
||||||
|
}
|
||||||
|
remote_version = None
|
||||||
|
retries = 0
|
||||||
|
while retries < 3:
|
||||||
|
try:
|
||||||
|
response = requests.get(download_url, headers=headers)
|
||||||
|
remote_version = response.text
|
||||||
|
log.debug('New version found: %s', remote_version)
|
||||||
|
break
|
||||||
|
except IOError:
|
||||||
|
log.exception('Unable to connect to OpenLP server to download version file')
|
||||||
|
retries += 1
|
||||||
|
else:
|
||||||
|
self.no_internet.emit()
|
||||||
|
if remote_version and LooseVersion(remote_version) > LooseVersion(self.current_version['full']):
|
||||||
|
self.new_version.emit(remote_version)
|
||||||
|
self.quit.emit()
|
||||||
|
|
||||||
|
|
||||||
def get_application_version():
|
def update_check_date():
|
||||||
|
"""
|
||||||
|
Save when we last checked for an update
|
||||||
|
"""
|
||||||
|
Settings().setValue('core/last version test', date.today().strftime('%Y-%m-%d'))
|
||||||
|
|
||||||
|
|
||||||
|
def check_for_update(parent):
|
||||||
|
"""
|
||||||
|
Run a thread to download and check the version of OpenLP
|
||||||
|
|
||||||
|
:param MainWindow parent: The parent object for the thread. Usually the OpenLP main window.
|
||||||
|
"""
|
||||||
|
last_check_date = Settings().value('core/last version test')
|
||||||
|
if date.today().strftime('%Y-%m-%d') <= last_check_date:
|
||||||
|
log.debug('Version check skipped, last checked today')
|
||||||
|
return
|
||||||
|
worker = VersionWorker(last_check_date, get_version())
|
||||||
|
worker.new_version.connect(parent.on_new_version)
|
||||||
|
worker.quit.connect(update_check_date)
|
||||||
|
# TODO: Use this to figure out if there's an Internet connection?
|
||||||
|
# worker.no_internet.connect(parent.on_no_internet)
|
||||||
|
run_thread(parent, worker, 'version')
|
||||||
|
|
||||||
|
|
||||||
|
def get_version():
|
||||||
"""
|
"""
|
||||||
Returns the application version of the running instance of OpenLP::
|
Returns the application version of the running instance of OpenLP::
|
||||||
|
|
||||||
|
@ -150,55 +199,3 @@ def get_application_version():
|
||||||
else:
|
else:
|
||||||
log.info('Openlp version {version}'.format(version=APPLICATION_VERSION['version']))
|
log.info('Openlp version {version}'.format(version=APPLICATION_VERSION['version']))
|
||||||
return APPLICATION_VERSION
|
return APPLICATION_VERSION
|
||||||
|
|
||||||
|
|
||||||
def check_latest_version(current_version):
|
|
||||||
"""
|
|
||||||
Check the latest version of OpenLP against the version file on the OpenLP
|
|
||||||
site.
|
|
||||||
|
|
||||||
**Rules around versions and version files:**
|
|
||||||
|
|
||||||
* If a version number has a build (i.e. -bzr1234), then it is a nightly.
|
|
||||||
* If a version number's minor version is an odd number, it is a development release.
|
|
||||||
* If a version number's minor version is an even number, it is a stable release.
|
|
||||||
|
|
||||||
:param current_version: The current version of OpenLP.
|
|
||||||
"""
|
|
||||||
version_string = current_version['full']
|
|
||||||
# set to prod in the distribution config file.
|
|
||||||
settings = Settings()
|
|
||||||
settings.beginGroup('core')
|
|
||||||
last_test = settings.value('last version test')
|
|
||||||
this_test = str(datetime.now().date())
|
|
||||||
settings.setValue('last version test', this_test)
|
|
||||||
settings.endGroup()
|
|
||||||
if last_test != this_test:
|
|
||||||
if current_version['build']:
|
|
||||||
req = urllib.request.Request('http://www.openlp.org/files/nightly_version.txt')
|
|
||||||
else:
|
|
||||||
version_parts = current_version['version'].split('.')
|
|
||||||
if int(version_parts[1]) % 2 != 0:
|
|
||||||
req = urllib.request.Request('http://www.openlp.org/files/dev_version.txt')
|
|
||||||
else:
|
|
||||||
req = urllib.request.Request('http://www.openlp.org/files/version.txt')
|
|
||||||
req.add_header('User-Agent', 'OpenLP/{version} {system}/{release}; '.format(version=current_version['full'],
|
|
||||||
system=platform.system(),
|
|
||||||
release=platform.release()))
|
|
||||||
remote_version = None
|
|
||||||
retries = 0
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
remote_version = str(urllib.request.urlopen(req, None,
|
|
||||||
timeout=CONNECTION_TIMEOUT).read().decode()).strip()
|
|
||||||
except (urllib.error.URLError, ConnectionError):
|
|
||||||
if retries > CONNECTION_RETRIES:
|
|
||||||
log.exception('Failed to download the latest OpenLP version file')
|
|
||||||
else:
|
|
||||||
retries += 1
|
|
||||||
time.sleep(0.1)
|
|
||||||
continue
|
|
||||||
break
|
|
||||||
if remote_version:
|
|
||||||
version_string = remote_version
|
|
||||||
return version_string
|
|
|
@ -70,7 +70,7 @@ class AlertForm(QtWidgets.QDialog, Ui_AlertDialog):
|
||||||
item_name = QtWidgets.QListWidgetItem(alert.text)
|
item_name = QtWidgets.QListWidgetItem(alert.text)
|
||||||
item_name.setData(QtCore.Qt.UserRole, alert.id)
|
item_name.setData(QtCore.Qt.UserRole, alert.id)
|
||||||
self.alert_list_widget.addItem(item_name)
|
self.alert_list_widget.addItem(item_name)
|
||||||
if alert.text == str(self.alert_text_edit.text()):
|
if alert.text == self.alert_text_edit.text():
|
||||||
self.item_id = alert.id
|
self.item_id = alert.id
|
||||||
self.alert_list_widget.setCurrentRow(self.alert_list_widget.row(item_name))
|
self.alert_list_widget.setCurrentRow(self.alert_list_widget.row(item_name))
|
||||||
|
|
||||||
|
|
|
@ -32,9 +32,6 @@ class AlertsTab(SettingsTab):
|
||||||
"""
|
"""
|
||||||
AlertsTab is the alerts settings tab in the settings dialog.
|
AlertsTab is the alerts settings tab in the settings dialog.
|
||||||
"""
|
"""
|
||||||
def __init__(self, parent, name, visible_title, icon_path):
|
|
||||||
super(AlertsTab, self).__init__(parent, name, visible_title, icon_path)
|
|
||||||
|
|
||||||
def setupUi(self):
|
def setupUi(self):
|
||||||
self.setObjectName('AlertsTab')
|
self.setObjectName('AlertsTab')
|
||||||
super(AlertsTab, self).setupUi()
|
super(AlertsTab, self).setupUi()
|
||||||
|
|
|
@ -93,7 +93,7 @@ class BGExtract(RegistryProperties):
|
||||||
NAME = 'BibleGateway'
|
NAME = 'BibleGateway'
|
||||||
|
|
||||||
def __init__(self, proxy_url=None):
|
def __init__(self, proxy_url=None):
|
||||||
log.debug('BGExtract.init("{url}")'.format(url=proxy_url))
|
log.debug('BGExtract.init(proxy_url="{url}")'.format(url=proxy_url))
|
||||||
self.proxy_url = proxy_url
|
self.proxy_url = proxy_url
|
||||||
socket.setdefaulttimeout(30)
|
socket.setdefaulttimeout(30)
|
||||||
|
|
||||||
|
@ -285,15 +285,10 @@ class BGExtract(RegistryProperties):
|
||||||
log.debug('BGExtract.get_books_from_http("{version}")'.format(version=version))
|
log.debug('BGExtract.get_books_from_http("{version}")'.format(version=version))
|
||||||
url_params = urllib.parse.urlencode({'action': 'getVersionInfo', 'vid': '{version}'.format(version=version)})
|
url_params = urllib.parse.urlencode({'action': 'getVersionInfo', 'vid': '{version}'.format(version=version)})
|
||||||
reference_url = 'http://www.biblegateway.com/versions/?{url}#books'.format(url=url_params)
|
reference_url = 'http://www.biblegateway.com/versions/?{url}#books'.format(url=url_params)
|
||||||
page = get_web_page(reference_url)
|
page_source = get_web_page(reference_url)
|
||||||
if not page:
|
if not page_source:
|
||||||
send_error_message('download')
|
send_error_message('download')
|
||||||
return None
|
return None
|
||||||
page_source = page.read()
|
|
||||||
try:
|
|
||||||
page_source = str(page_source, 'utf8')
|
|
||||||
except UnicodeDecodeError:
|
|
||||||
page_source = str(page_source, 'cp1251')
|
|
||||||
try:
|
try:
|
||||||
soup = BeautifulSoup(page_source, 'lxml')
|
soup = BeautifulSoup(page_source, 'lxml')
|
||||||
except Exception:
|
except Exception:
|
||||||
|
@ -759,7 +754,7 @@ class HTTPBible(BibleImport, RegistryProperties):
|
||||||
return BiblesResourcesDB.get_verse_count(book_id, chapter)
|
return BiblesResourcesDB.get_verse_count(book_id, chapter)
|
||||||
|
|
||||||
|
|
||||||
def get_soup_for_bible_ref(reference_url, header=None, pre_parse_regex=None, pre_parse_substitute=None):
|
def get_soup_for_bible_ref(reference_url, headers=None, pre_parse_regex=None, pre_parse_substitute=None):
|
||||||
"""
|
"""
|
||||||
Gets a webpage and returns a parsed and optionally cleaned soup or None.
|
Gets a webpage and returns a parsed and optionally cleaned soup or None.
|
||||||
|
|
||||||
|
@ -772,15 +767,15 @@ def get_soup_for_bible_ref(reference_url, header=None, pre_parse_regex=None, pre
|
||||||
if not reference_url:
|
if not reference_url:
|
||||||
return None
|
return None
|
||||||
try:
|
try:
|
||||||
page = get_web_page(reference_url, header, True)
|
page_source = get_web_page(reference_url, headers, update_openlp=True)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
page = None
|
log.exception('Unable to download Bible %s, unknown exception occurred', reference_url)
|
||||||
if not page:
|
page_source = None
|
||||||
|
if not page_source:
|
||||||
send_error_message('download')
|
send_error_message('download')
|
||||||
return None
|
return None
|
||||||
page_source = page.read()
|
|
||||||
if pre_parse_regex and pre_parse_substitute is not None:
|
if pre_parse_regex and pre_parse_substitute is not None:
|
||||||
page_source = re.sub(pre_parse_regex, pre_parse_substitute, page_source.decode())
|
page_source = re.sub(pre_parse_regex, pre_parse_substitute, page_source)
|
||||||
soup = None
|
soup = None
|
||||||
try:
|
try:
|
||||||
soup = BeautifulSoup(page_source, 'lxml')
|
soup = BeautifulSoup(page_source, 'lxml')
|
||||||
|
|
|
@ -34,9 +34,6 @@ class CustomTab(SettingsTab):
|
||||||
"""
|
"""
|
||||||
CustomTab is the Custom settings tab in the settings dialog.
|
CustomTab is the Custom settings tab in the settings dialog.
|
||||||
"""
|
"""
|
||||||
def __init__(self, parent, title, visible_title, icon_path):
|
|
||||||
super(CustomTab, self).__init__(parent, title, visible_title, icon_path)
|
|
||||||
|
|
||||||
def setupUi(self):
|
def setupUi(self):
|
||||||
self.setObjectName('CustomTab')
|
self.setObjectName('CustomTab')
|
||||||
super(CustomTab, self).setupUi()
|
super(CustomTab, self).setupUi()
|
||||||
|
|
|
@ -29,7 +29,7 @@ from openlp.core.common import Settings, translate
|
||||||
from openlp.core.lib import Plugin, StringContent, ImageSource, build_icon
|
from openlp.core.lib import Plugin, StringContent, ImageSource, build_icon
|
||||||
from openlp.core.lib.db import Manager
|
from openlp.core.lib.db import Manager
|
||||||
from openlp.plugins.images.endpoint import api_images_endpoint, images_endpoint
|
from openlp.plugins.images.endpoint import api_images_endpoint, images_endpoint
|
||||||
from openlp.plugins.images.lib import ImageMediaItem, ImageTab
|
from openlp.plugins.images.lib import ImageMediaItem, ImageTab, upgrade
|
||||||
from openlp.plugins.images.lib.db import init_schema
|
from openlp.plugins.images.lib.db import init_schema
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
@ -50,7 +50,7 @@ class ImagePlugin(Plugin):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(ImagePlugin, self).__init__('images', __default_settings__, ImageMediaItem, ImageTab)
|
super(ImagePlugin, self).__init__('images', __default_settings__, ImageMediaItem, ImageTab)
|
||||||
self.manager = Manager('images', init_schema)
|
self.manager = Manager('images', init_schema, upgrade_mod=upgrade)
|
||||||
self.weight = -7
|
self.weight = -7
|
||||||
self.icon_path = ':/plugins/plugin_images.png'
|
self.icon_path = ':/plugins/plugin_images.png'
|
||||||
self.icon = build_icon(self.icon_path)
|
self.icon = build_icon(self.icon_path)
|
||||||
|
|
|
@ -22,11 +22,10 @@
|
||||||
"""
|
"""
|
||||||
The :mod:`db` module provides the database and schema that is the backend for the Images plugin.
|
The :mod:`db` module provides the database and schema that is the backend for the Images plugin.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from sqlalchemy import Column, ForeignKey, Table, types
|
from sqlalchemy import Column, ForeignKey, Table, types
|
||||||
from sqlalchemy.orm import mapper
|
from sqlalchemy.orm import mapper
|
||||||
|
|
||||||
from openlp.core.lib.db import BaseModel, init_db
|
from openlp.core.lib.db import BaseModel, PathType, init_db
|
||||||
|
|
||||||
|
|
||||||
class ImageGroups(BaseModel):
|
class ImageGroups(BaseModel):
|
||||||
|
@ -65,7 +64,7 @@ def init_schema(url):
|
||||||
|
|
||||||
* id
|
* id
|
||||||
* group_id
|
* group_id
|
||||||
* filename
|
* file_path
|
||||||
"""
|
"""
|
||||||
session, metadata = init_db(url)
|
session, metadata = init_db(url)
|
||||||
|
|
||||||
|
@ -80,7 +79,7 @@ def init_schema(url):
|
||||||
image_filenames_table = Table('image_filenames', metadata,
|
image_filenames_table = Table('image_filenames', metadata,
|
||||||
Column('id', types.Integer(), primary_key=True),
|
Column('id', types.Integer(), primary_key=True),
|
||||||
Column('group_id', types.Integer(), ForeignKey('image_groups.id'), default=None),
|
Column('group_id', types.Integer(), ForeignKey('image_groups.id'), default=None),
|
||||||
Column('filename', types.Unicode(255), nullable=False)
|
Column('file_path', PathType(), nullable=False)
|
||||||
)
|
)
|
||||||
|
|
||||||
mapper(ImageGroups, image_groups_table)
|
mapper(ImageGroups, image_groups_table)
|
||||||
|
|
|
@ -31,9 +31,6 @@ class ImageTab(SettingsTab):
|
||||||
"""
|
"""
|
||||||
ImageTab is the images settings tab in the settings dialog.
|
ImageTab is the images settings tab in the settings dialog.
|
||||||
"""
|
"""
|
||||||
def __init__(self, parent, name, visible_title, icon_path):
|
|
||||||
super(ImageTab, self).__init__(parent, name, visible_title, icon_path)
|
|
||||||
|
|
||||||
def setupUi(self):
|
def setupUi(self):
|
||||||
self.setObjectName('ImagesTab')
|
self.setObjectName('ImagesTab')
|
||||||
super(ImageTab, self).setupUi()
|
super(ImageTab, self).setupUi()
|
||||||
|
|
|
@ -21,7 +21,6 @@
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
|
||||||
|
|
||||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||||
|
|
||||||
|
@ -99,11 +98,11 @@ class ImageMediaItem(MediaManagerItem):
|
||||||
self.list_view.setIconSize(QtCore.QSize(88, 50))
|
self.list_view.setIconSize(QtCore.QSize(88, 50))
|
||||||
self.list_view.setIndentation(self.list_view.default_indentation)
|
self.list_view.setIndentation(self.list_view.default_indentation)
|
||||||
self.list_view.allow_internal_dnd = True
|
self.list_view.allow_internal_dnd = True
|
||||||
self.service_path = os.path.join(str(AppLocation.get_section_data_path(self.settings_section)), 'thumbnails')
|
self.service_path = AppLocation.get_section_data_path(self.settings_section) / 'thumbnails'
|
||||||
check_directory_exists(Path(self.service_path))
|
check_directory_exists(self.service_path)
|
||||||
# Load images from the database
|
# Load images from the database
|
||||||
self.load_full_list(
|
self.load_full_list(
|
||||||
self.manager.get_all_objects(ImageFilenames, order_by_ref=ImageFilenames.filename), initial_load=True)
|
self.manager.get_all_objects(ImageFilenames, order_by_ref=ImageFilenames.file_path), initial_load=True)
|
||||||
|
|
||||||
def add_list_view_to_toolbar(self):
|
def add_list_view_to_toolbar(self):
|
||||||
"""
|
"""
|
||||||
|
@ -211,8 +210,8 @@ class ImageMediaItem(MediaManagerItem):
|
||||||
"""
|
"""
|
||||||
images = self.manager.get_all_objects(ImageFilenames, ImageFilenames.group_id == image_group.id)
|
images = self.manager.get_all_objects(ImageFilenames, ImageFilenames.group_id == image_group.id)
|
||||||
for image in images:
|
for image in images:
|
||||||
delete_file(Path(self.service_path, os.path.split(image.filename)[1]))
|
delete_file(self.service_path / image.file_path.name)
|
||||||
delete_file(Path(self.generate_thumbnail_path(image)))
|
delete_file(self.generate_thumbnail_path(image))
|
||||||
self.manager.delete_object(ImageFilenames, image.id)
|
self.manager.delete_object(ImageFilenames, image.id)
|
||||||
image_groups = self.manager.get_all_objects(ImageGroups, ImageGroups.parent_id == image_group.id)
|
image_groups = self.manager.get_all_objects(ImageGroups, ImageGroups.parent_id == image_group.id)
|
||||||
for group in image_groups:
|
for group in image_groups:
|
||||||
|
@ -234,8 +233,8 @@ class ImageMediaItem(MediaManagerItem):
|
||||||
if row_item:
|
if row_item:
|
||||||
item_data = row_item.data(0, QtCore.Qt.UserRole)
|
item_data = row_item.data(0, QtCore.Qt.UserRole)
|
||||||
if isinstance(item_data, ImageFilenames):
|
if isinstance(item_data, ImageFilenames):
|
||||||
delete_file(Path(self.service_path, row_item.text(0)))
|
delete_file(self.service_path / row_item.text(0))
|
||||||
delete_file(Path(self.generate_thumbnail_path(item_data)))
|
delete_file(self.generate_thumbnail_path(item_data))
|
||||||
if item_data.group_id == 0:
|
if item_data.group_id == 0:
|
||||||
self.list_view.takeTopLevelItem(self.list_view.indexOfTopLevelItem(row_item))
|
self.list_view.takeTopLevelItem(self.list_view.indexOfTopLevelItem(row_item))
|
||||||
else:
|
else:
|
||||||
|
@ -326,17 +325,19 @@ class ImageMediaItem(MediaManagerItem):
|
||||||
"""
|
"""
|
||||||
Generate a path to the thumbnail
|
Generate a path to the thumbnail
|
||||||
|
|
||||||
:param image: An instance of ImageFileNames
|
:param openlp.plugins.images.lib.db.ImageFilenames image: The image to generate the thumbnail path for.
|
||||||
:return: A path to the thumbnail of type str
|
:return: A path to the thumbnail
|
||||||
|
:rtype: openlp.core.common.path.Path
|
||||||
"""
|
"""
|
||||||
ext = os.path.splitext(image.filename)[1].lower()
|
ext = image.file_path.suffix.lower()
|
||||||
return os.path.join(self.service_path, '{}{}'.format(str(image.id), ext))
|
return self.service_path / '{name:d}{ext}'.format(name=image.id, ext=ext)
|
||||||
|
|
||||||
def load_full_list(self, images, initial_load=False, open_group=None):
|
def load_full_list(self, images, initial_load=False, open_group=None):
|
||||||
"""
|
"""
|
||||||
Replace the list of images and groups in the interface.
|
Replace the list of images and groups in the interface.
|
||||||
|
|
||||||
:param images: A List of Image Filenames objects that will be used to reload the mediamanager list.
|
:param list[openlp.plugins.images.lib.db.ImageFilenames] images: A List of Image Filenames objects that will be
|
||||||
|
used to reload the mediamanager list.
|
||||||
:param initial_load: When set to False, the busy cursor and progressbar will be shown while loading images.
|
:param initial_load: When set to False, the busy cursor and progressbar will be shown while loading images.
|
||||||
:param open_group: ImageGroups object of the group that must be expanded after reloading the list in the
|
:param open_group: ImageGroups object of the group that must be expanded after reloading the list in the
|
||||||
interface.
|
interface.
|
||||||
|
@ -352,34 +353,34 @@ class ImageMediaItem(MediaManagerItem):
|
||||||
self.expand_group(open_group.id)
|
self.expand_group(open_group.id)
|
||||||
# Sort the images by its filename considering language specific.
|
# Sort the images by its filename considering language specific.
|
||||||
# characters.
|
# characters.
|
||||||
images.sort(key=lambda image_object: get_locale_key(os.path.split(str(image_object.filename))[1]))
|
images.sort(key=lambda image_object: get_locale_key(image_object.file_path.name))
|
||||||
for image_file in images:
|
for image in images:
|
||||||
log.debug('Loading image: {name}'.format(name=image_file.filename))
|
log.debug('Loading image: {name}'.format(name=image.file_path))
|
||||||
filename = os.path.split(image_file.filename)[1]
|
file_name = image.file_path.name
|
||||||
thumb = self.generate_thumbnail_path(image_file)
|
thumbnail_path = self.generate_thumbnail_path(image)
|
||||||
if not os.path.exists(image_file.filename):
|
if not image.file_path.exists():
|
||||||
icon = build_icon(':/general/general_delete.png')
|
icon = build_icon(':/general/general_delete.png')
|
||||||
else:
|
else:
|
||||||
if validate_thumb(image_file.filename, thumb):
|
if validate_thumb(image.file_path, thumbnail_path):
|
||||||
icon = build_icon(thumb)
|
icon = build_icon(thumbnail_path)
|
||||||
else:
|
else:
|
||||||
icon = create_thumb(image_file.filename, thumb)
|
icon = create_thumb(image.file_path, thumbnail_path)
|
||||||
item_name = QtWidgets.QTreeWidgetItem([filename])
|
item_name = QtWidgets.QTreeWidgetItem([file_name])
|
||||||
item_name.setText(0, filename)
|
item_name.setText(0, file_name)
|
||||||
item_name.setIcon(0, icon)
|
item_name.setIcon(0, icon)
|
||||||
item_name.setToolTip(0, image_file.filename)
|
item_name.setToolTip(0, str(image.file_path))
|
||||||
item_name.setData(0, QtCore.Qt.UserRole, image_file)
|
item_name.setData(0, QtCore.Qt.UserRole, image)
|
||||||
if image_file.group_id == 0:
|
if image.group_id == 0:
|
||||||
self.list_view.addTopLevelItem(item_name)
|
self.list_view.addTopLevelItem(item_name)
|
||||||
else:
|
else:
|
||||||
group_items[image_file.group_id].addChild(item_name)
|
group_items[image.group_id].addChild(item_name)
|
||||||
if not initial_load:
|
if not initial_load:
|
||||||
self.main_window.increment_progress_bar()
|
self.main_window.increment_progress_bar()
|
||||||
if not initial_load:
|
if not initial_load:
|
||||||
self.main_window.finished_progress_bar()
|
self.main_window.finished_progress_bar()
|
||||||
self.application.set_normal_cursor()
|
self.application.set_normal_cursor()
|
||||||
|
|
||||||
def validate_and_load(self, files, target_group=None):
|
def validate_and_load(self, file_paths, target_group=None):
|
||||||
"""
|
"""
|
||||||
Process a list for files either from the File Dialog or from Drag and Drop.
|
Process a list for files either from the File Dialog or from Drag and Drop.
|
||||||
This method is overloaded from MediaManagerItem.
|
This method is overloaded from MediaManagerItem.
|
||||||
|
@ -388,15 +389,15 @@ class ImageMediaItem(MediaManagerItem):
|
||||||
:param target_group: The QTreeWidgetItem of the group that will be the parent of the added files
|
:param target_group: The QTreeWidgetItem of the group that will be the parent of the added files
|
||||||
"""
|
"""
|
||||||
self.application.set_normal_cursor()
|
self.application.set_normal_cursor()
|
||||||
self.load_list(files, target_group)
|
self.load_list(file_paths, target_group)
|
||||||
last_dir = os.path.split(files[0])[0]
|
last_dir = file_paths[0].parent
|
||||||
Settings().setValue(self.settings_section + '/last directory', Path(last_dir))
|
Settings().setValue(self.settings_section + '/last directory', last_dir)
|
||||||
|
|
||||||
def load_list(self, images, target_group=None, initial_load=False):
|
def load_list(self, image_paths, target_group=None, initial_load=False):
|
||||||
"""
|
"""
|
||||||
Add new images to the database. This method is called when adding images using the Add button or DnD.
|
Add new images to the database. This method is called when adding images using the Add button or DnD.
|
||||||
|
|
||||||
:param images: A List of strings containing the filenames of the files to be loaded
|
:param list[openlp.core.common.Path] image_paths: A list of file paths to the images to be loaded
|
||||||
:param target_group: The QTreeWidgetItem of the group that will be the parent of the added files
|
:param target_group: The QTreeWidgetItem of the group that will be the parent of the added files
|
||||||
:param initial_load: When set to False, the busy cursor and progressbar will be shown while loading images
|
:param initial_load: When set to False, the busy cursor and progressbar will be shown while loading images
|
||||||
"""
|
"""
|
||||||
|
@ -429,7 +430,7 @@ class ImageMediaItem(MediaManagerItem):
|
||||||
else:
|
else:
|
||||||
self.choose_group_form.existing_radio_button.setDisabled(False)
|
self.choose_group_form.existing_radio_button.setDisabled(False)
|
||||||
self.choose_group_form.group_combobox.setDisabled(False)
|
self.choose_group_form.group_combobox.setDisabled(False)
|
||||||
# Ask which group the images should be saved in
|
# Ask which group the image_paths should be saved in
|
||||||
if self.choose_group_form.exec(selected_group=preselect_group):
|
if self.choose_group_form.exec(selected_group=preselect_group):
|
||||||
if self.choose_group_form.nogroup_radio_button.isChecked():
|
if self.choose_group_form.nogroup_radio_button.isChecked():
|
||||||
# User chose 'No group'
|
# User chose 'No group'
|
||||||
|
@ -461,33 +462,33 @@ class ImageMediaItem(MediaManagerItem):
|
||||||
return
|
return
|
||||||
# Initialize busy cursor and progress bar
|
# Initialize busy cursor and progress bar
|
||||||
self.application.set_busy_cursor()
|
self.application.set_busy_cursor()
|
||||||
self.main_window.display_progress_bar(len(images))
|
self.main_window.display_progress_bar(len(image_paths))
|
||||||
# Save the new images in the database
|
# Save the new image_paths in the database
|
||||||
self.save_new_images_list(images, group_id=parent_group.id, reload_list=False)
|
self.save_new_images_list(image_paths, group_id=parent_group.id, reload_list=False)
|
||||||
self.load_full_list(self.manager.get_all_objects(ImageFilenames, order_by_ref=ImageFilenames.filename),
|
self.load_full_list(self.manager.get_all_objects(ImageFilenames, order_by_ref=ImageFilenames.file_path),
|
||||||
initial_load=initial_load, open_group=parent_group)
|
initial_load=initial_load, open_group=parent_group)
|
||||||
self.application.set_normal_cursor()
|
self.application.set_normal_cursor()
|
||||||
|
|
||||||
def save_new_images_list(self, images_list, group_id=0, reload_list=True):
|
def save_new_images_list(self, image_paths, group_id=0, reload_list=True):
|
||||||
"""
|
"""
|
||||||
Convert a list of image filenames to ImageFilenames objects and save them in the database.
|
Convert a list of image filenames to ImageFilenames objects and save them in the database.
|
||||||
|
|
||||||
:param images_list: A List of strings containing image filenames
|
:param list[Path] image_paths: A List of file paths to image
|
||||||
:param group_id: The ID of the group to save the images in
|
:param group_id: The ID of the group to save the images in
|
||||||
:param reload_list: This boolean is set to True when the list in the interface should be reloaded after saving
|
:param reload_list: This boolean is set to True when the list in the interface should be reloaded after saving
|
||||||
the new images
|
the new images
|
||||||
"""
|
"""
|
||||||
for filename in images_list:
|
for image_path in image_paths:
|
||||||
if not isinstance(filename, str):
|
if not isinstance(image_path, Path):
|
||||||
continue
|
continue
|
||||||
log.debug('Adding new image: {name}'.format(name=filename))
|
log.debug('Adding new image: {name}'.format(name=image_path))
|
||||||
image_file = ImageFilenames()
|
image_file = ImageFilenames()
|
||||||
image_file.group_id = group_id
|
image_file.group_id = group_id
|
||||||
image_file.filename = str(filename)
|
image_file.file_path = image_path
|
||||||
self.manager.save_object(image_file)
|
self.manager.save_object(image_file)
|
||||||
self.main_window.increment_progress_bar()
|
self.main_window.increment_progress_bar()
|
||||||
if reload_list and images_list:
|
if reload_list and image_paths:
|
||||||
self.load_full_list(self.manager.get_all_objects(ImageFilenames, order_by_ref=ImageFilenames.filename))
|
self.load_full_list(self.manager.get_all_objects(ImageFilenames, order_by_ref=ImageFilenames.file_path))
|
||||||
|
|
||||||
def dnd_move_internal(self, target):
|
def dnd_move_internal(self, target):
|
||||||
"""
|
"""
|
||||||
|
@ -581,8 +582,8 @@ class ImageMediaItem(MediaManagerItem):
|
||||||
return False
|
return False
|
||||||
# Find missing files
|
# Find missing files
|
||||||
for image in images:
|
for image in images:
|
||||||
if not os.path.exists(image.filename):
|
if not image.file_path.exists():
|
||||||
missing_items_file_names.append(image.filename)
|
missing_items_file_names.append(str(image.file_path))
|
||||||
# We cannot continue, as all images do not exist.
|
# We cannot continue, as all images do not exist.
|
||||||
if not images:
|
if not images:
|
||||||
if not remote:
|
if not remote:
|
||||||
|
@ -601,9 +602,9 @@ class ImageMediaItem(MediaManagerItem):
|
||||||
return False
|
return False
|
||||||
# Continue with the existing images.
|
# Continue with the existing images.
|
||||||
for image in images:
|
for image in images:
|
||||||
name = os.path.split(image.filename)[1]
|
name = image.file_path.name
|
||||||
thumbnail = self.generate_thumbnail_path(image)
|
thumbnail_path = self.generate_thumbnail_path(image)
|
||||||
service_item.add_from_image(image.filename, name, background, thumbnail)
|
service_item.add_from_image(str(image.file_path), name, background, str(thumbnail_path))
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def check_group_exists(self, new_group):
|
def check_group_exists(self, new_group):
|
||||||
|
@ -640,7 +641,7 @@ class ImageMediaItem(MediaManagerItem):
|
||||||
if not self.check_group_exists(new_group):
|
if not self.check_group_exists(new_group):
|
||||||
if self.manager.save_object(new_group):
|
if self.manager.save_object(new_group):
|
||||||
self.load_full_list(self.manager.get_all_objects(
|
self.load_full_list(self.manager.get_all_objects(
|
||||||
ImageFilenames, order_by_ref=ImageFilenames.filename))
|
ImageFilenames, order_by_ref=ImageFilenames.file_path))
|
||||||
self.expand_group(new_group.id)
|
self.expand_group(new_group.id)
|
||||||
self.fill_groups_combobox(self.choose_group_form.group_combobox)
|
self.fill_groups_combobox(self.choose_group_form.group_combobox)
|
||||||
self.fill_groups_combobox(self.add_group_form.parent_group_combobox)
|
self.fill_groups_combobox(self.add_group_form.parent_group_combobox)
|
||||||
|
@ -675,9 +676,9 @@ class ImageMediaItem(MediaManagerItem):
|
||||||
if not isinstance(bitem.data(0, QtCore.Qt.UserRole), ImageFilenames):
|
if not isinstance(bitem.data(0, QtCore.Qt.UserRole), ImageFilenames):
|
||||||
# Only continue when an image is selected.
|
# Only continue when an image is selected.
|
||||||
return
|
return
|
||||||
filename = bitem.data(0, QtCore.Qt.UserRole).filename
|
file_path = bitem.data(0, QtCore.Qt.UserRole).file_path
|
||||||
if os.path.exists(filename):
|
if file_path.exists():
|
||||||
if self.live_controller.display.direct_image(filename, background):
|
if self.live_controller.display.direct_image(str(file_path), background):
|
||||||
self.reset_action.setVisible(True)
|
self.reset_action.setVisible(True)
|
||||||
else:
|
else:
|
||||||
critical_error_message_box(
|
critical_error_message_box(
|
||||||
|
@ -687,22 +688,22 @@ class ImageMediaItem(MediaManagerItem):
|
||||||
critical_error_message_box(
|
critical_error_message_box(
|
||||||
UiStrings().LiveBGError,
|
UiStrings().LiveBGError,
|
||||||
translate('ImagePlugin.MediaItem', 'There was a problem replacing your background, '
|
translate('ImagePlugin.MediaItem', 'There was a problem replacing your background, '
|
||||||
'the image file "{name}" no longer exists.').format(name=filename))
|
'the image file "{name}" no longer exists.').format(name=file_path))
|
||||||
|
|
||||||
def search(self, string, show_error=True):
|
def search(self, string, show_error=True):
|
||||||
"""
|
"""
|
||||||
Perform a search on the image file names.
|
Perform a search on the image file names.
|
||||||
|
|
||||||
:param string: The glob to search for
|
:param str string: The glob to search for
|
||||||
:param show_error: Unused.
|
:param bool show_error: Unused.
|
||||||
"""
|
"""
|
||||||
files = self.manager.get_all_objects(
|
files = self.manager.get_all_objects(
|
||||||
ImageFilenames, filter_clause=ImageFilenames.filename.contains(string),
|
ImageFilenames, filter_clause=ImageFilenames.file_path.contains(string),
|
||||||
order_by_ref=ImageFilenames.filename)
|
order_by_ref=ImageFilenames.file_path)
|
||||||
results = []
|
results = []
|
||||||
for file_object in files:
|
for file_object in files:
|
||||||
filename = os.path.split(str(file_object.filename))[1]
|
file_name = file_object.file_path.name
|
||||||
results.append([file_object.filename, filename])
|
results.append([str(file_object.file_path), file_name])
|
||||||
return results
|
return results
|
||||||
|
|
||||||
def create_item_from_id(self, item_id):
|
def create_item_from_id(self, item_id):
|
||||||
|
@ -711,8 +712,9 @@ class ImageMediaItem(MediaManagerItem):
|
||||||
|
|
||||||
:param item_id: Id to make live
|
:param item_id: Id to make live
|
||||||
"""
|
"""
|
||||||
|
item_id = Path(item_id)
|
||||||
item = QtWidgets.QTreeWidgetItem()
|
item = QtWidgets.QTreeWidgetItem()
|
||||||
item_data = self.manager.get_object_filtered(ImageFilenames, ImageFilenames.filename == item_id)
|
item_data = self.manager.get_object_filtered(ImageFilenames, ImageFilenames.file_path == item_id)
|
||||||
item.setText(0, os.path.basename(item_data.filename))
|
item.setText(0, item_data.file_path.name)
|
||||||
item.setData(0, QtCore.Qt.UserRole, item_data)
|
item.setData(0, QtCore.Qt.UserRole, item_data)
|
||||||
return item
|
return item
|
||||||
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
# -*- 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 #
|
||||||
|
###############################################################################
|
||||||
|
"""
|
||||||
|
The :mod:`upgrade` module provides the migration path for the OLP Paths database
|
||||||
|
"""
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from sqlalchemy import Column, Table
|
||||||
|
|
||||||
|
from openlp.core.common import AppLocation
|
||||||
|
from openlp.core.common.db import drop_columns
|
||||||
|
from openlp.core.common.json import OpenLPJsonEncoder
|
||||||
|
from openlp.core.common.path import Path
|
||||||
|
from openlp.core.lib.db import PathType, get_upgrade_op
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
__version__ = 2
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade_1(session, metadata):
|
||||||
|
"""
|
||||||
|
Version 1 upgrade - old db might/might not be versioned.
|
||||||
|
"""
|
||||||
|
log.debug('Skipping upgrade_1 of files DB - not used')
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade_2(session, metadata):
|
||||||
|
"""
|
||||||
|
Version 2 upgrade - Move file path from old db to JSON encoded path to new db. Added during 2.5 dev
|
||||||
|
"""
|
||||||
|
log.debug('Starting upgrade_2 for file_path to JSON')
|
||||||
|
old_table = Table('image_filenames', metadata, autoload=True)
|
||||||
|
if 'file_path' not in [col.name for col in old_table.c.values()]:
|
||||||
|
op = get_upgrade_op(session)
|
||||||
|
op.add_column('image_filenames', Column('file_path', PathType()))
|
||||||
|
conn = op.get_bind()
|
||||||
|
results = conn.execute('SELECT * FROM image_filenames')
|
||||||
|
data_path = AppLocation.get_data_path()
|
||||||
|
for row in results.fetchall():
|
||||||
|
file_path_json = json.dumps(Path(row.filename), cls=OpenLPJsonEncoder, base_path=data_path)
|
||||||
|
sql = 'UPDATE image_filenames SET file_path = \'{file_path_json}\' WHERE id = {id}'.format(
|
||||||
|
file_path_json=file_path_json, id=row.id)
|
||||||
|
conn.execute(sql)
|
||||||
|
# Drop old columns
|
||||||
|
if metadata.bind.url.get_dialect().name == 'sqlite':
|
||||||
|
drop_columns(op, 'image_filenames', ['filename', ])
|
||||||
|
else:
|
||||||
|
op.drop_constraint('image_filenames', 'foreignkey')
|
||||||
|
op.drop_column('image_filenames', 'filenames')
|
|
@ -32,11 +32,14 @@
|
||||||
# http://nxsy.org/comparing-documents-with-openoffice-and-python
|
# http://nxsy.org/comparing-documents-with-openoffice-and-python
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from openlp.core.common import is_win, Registry, delete_file
|
from PyQt5 import QtCore
|
||||||
from openlp.core.common.path import Path
|
|
||||||
|
from openlp.core.common import Registry, delete_file, get_uno_command, get_uno_instance, is_win
|
||||||
|
from openlp.core.lib import ScreenList
|
||||||
|
from openlp.plugins.presentations.lib.presentationcontroller import PresentationController, PresentationDocument, \
|
||||||
|
TextType
|
||||||
|
|
||||||
if is_win():
|
if is_win():
|
||||||
from win32com.client import Dispatch
|
from win32com.client import Dispatch
|
||||||
|
@ -55,14 +58,6 @@ else:
|
||||||
except ImportError:
|
except ImportError:
|
||||||
uno_available = False
|
uno_available = False
|
||||||
|
|
||||||
from PyQt5 import QtCore
|
|
||||||
|
|
||||||
from openlp.core.lib import ScreenList
|
|
||||||
from openlp.core.common import get_uno_command, get_uno_instance
|
|
||||||
from openlp.plugins.presentations.lib.presentationcontroller import PresentationController, PresentationDocument, \
|
|
||||||
TextType
|
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@ -203,12 +198,15 @@ class ImpressDocument(PresentationDocument):
|
||||||
Class which holds information and controls a single presentation.
|
Class which holds information and controls a single presentation.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, controller, presentation):
|
def __init__(self, controller, document_path):
|
||||||
"""
|
"""
|
||||||
Constructor, store information about the file and initialise.
|
Constructor, store information about the file and initialise.
|
||||||
|
|
||||||
|
:param openlp.core.common.path.Path document_path: File path for the document to load
|
||||||
|
:rtype: None
|
||||||
"""
|
"""
|
||||||
log.debug('Init Presentation OpenOffice')
|
log.debug('Init Presentation OpenOffice')
|
||||||
super(ImpressDocument, self).__init__(controller, presentation)
|
super().__init__(controller, document_path)
|
||||||
self.document = None
|
self.document = None
|
||||||
self.presentation = None
|
self.presentation = None
|
||||||
self.control = None
|
self.control = None
|
||||||
|
@ -225,10 +223,9 @@ class ImpressDocument(PresentationDocument):
|
||||||
if desktop is None:
|
if desktop is None:
|
||||||
self.controller.start_process()
|
self.controller.start_process()
|
||||||
desktop = self.controller.get_com_desktop()
|
desktop = self.controller.get_com_desktop()
|
||||||
url = 'file:///' + self.file_path.replace('\\', '/').replace(':', '|').replace(' ', '%20')
|
|
||||||
else:
|
else:
|
||||||
desktop = self.controller.get_uno_desktop()
|
desktop = self.controller.get_uno_desktop()
|
||||||
url = uno.systemPathToFileUrl(self.file_path)
|
url = self.file_path.as_uri()
|
||||||
if desktop is None:
|
if desktop is None:
|
||||||
return False
|
return False
|
||||||
self.desktop = desktop
|
self.desktop = desktop
|
||||||
|
@ -254,11 +251,8 @@ class ImpressDocument(PresentationDocument):
|
||||||
log.debug('create thumbnails OpenOffice')
|
log.debug('create thumbnails OpenOffice')
|
||||||
if self.check_thumbnails():
|
if self.check_thumbnails():
|
||||||
return
|
return
|
||||||
if is_win():
|
temp_folder_path = self.get_temp_folder()
|
||||||
thumb_dir_url = 'file:///' + self.get_temp_folder().replace('\\', '/') \
|
thumb_dir_url = temp_folder_path.as_uri()
|
||||||
.replace(':', '|').replace(' ', '%20')
|
|
||||||
else:
|
|
||||||
thumb_dir_url = uno.systemPathToFileUrl(self.get_temp_folder())
|
|
||||||
properties = []
|
properties = []
|
||||||
properties.append(self.create_property('FilterName', 'impress_png_Export'))
|
properties.append(self.create_property('FilterName', 'impress_png_Export'))
|
||||||
properties = tuple(properties)
|
properties = tuple(properties)
|
||||||
|
@ -266,17 +260,17 @@ class ImpressDocument(PresentationDocument):
|
||||||
pages = doc.getDrawPages()
|
pages = doc.getDrawPages()
|
||||||
if not pages:
|
if not pages:
|
||||||
return
|
return
|
||||||
if not os.path.isdir(self.get_temp_folder()):
|
if not temp_folder_path.is_dir():
|
||||||
os.makedirs(self.get_temp_folder())
|
temp_folder_path.mkdir(parents=True)
|
||||||
for index in range(pages.getCount()):
|
for index in range(pages.getCount()):
|
||||||
page = pages.getByIndex(index)
|
page = pages.getByIndex(index)
|
||||||
doc.getCurrentController().setCurrentPage(page)
|
doc.getCurrentController().setCurrentPage(page)
|
||||||
url_path = '{path}/{name}.png'.format(path=thumb_dir_url, name=str(index + 1))
|
url_path = '{path}/{name:d}.png'.format(path=thumb_dir_url, name=index + 1)
|
||||||
path = os.path.join(self.get_temp_folder(), str(index + 1) + '.png')
|
path = temp_folder_path / '{number:d}.png'.format(number=index + 1)
|
||||||
try:
|
try:
|
||||||
doc.storeToURL(url_path, properties)
|
doc.storeToURL(url_path, properties)
|
||||||
self.convert_thumbnail(path, index + 1)
|
self.convert_thumbnail(path, index + 1)
|
||||||
delete_file(Path(path))
|
delete_file(path)
|
||||||
except ErrorCodeIOException as exception:
|
except ErrorCodeIOException as exception:
|
||||||
log.exception('ERROR! ErrorCodeIOException {error:d}'.format(error=exception.ErrCode))
|
log.exception('ERROR! ErrorCodeIOException {error:d}'.format(error=exception.ErrCode))
|
||||||
except:
|
except:
|
||||||
|
|
|
@ -19,15 +19,13 @@
|
||||||
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
|
||||||
|
|
||||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||||
|
|
||||||
from openlp.core.common import Registry, Settings, UiStrings, translate
|
from openlp.core.common import Registry, Settings, UiStrings, translate
|
||||||
from openlp.core.common.languagemanager import get_locale_key
|
from openlp.core.common.languagemanager import get_locale_key
|
||||||
from openlp.core.common.path import path_to_str
|
from openlp.core.common.path import Path, path_to_str, str_to_path
|
||||||
from openlp.core.lib import MediaManagerItem, ItemCapabilities, ServiceItemContext,\
|
from openlp.core.lib import MediaManagerItem, ItemCapabilities, ServiceItemContext,\
|
||||||
build_icon, check_item_selected, create_thumb, validate_thumb
|
build_icon, check_item_selected, create_thumb, validate_thumb
|
||||||
from openlp.core.lib.ui import critical_error_message_box, create_horizontal_adjusting_combo_box
|
from openlp.core.lib.ui import critical_error_message_box, create_horizontal_adjusting_combo_box
|
||||||
|
@ -128,7 +126,7 @@ class PresentationMediaItem(MediaManagerItem):
|
||||||
"""
|
"""
|
||||||
self.list_view.setIconSize(QtCore.QSize(88, 50))
|
self.list_view.setIconSize(QtCore.QSize(88, 50))
|
||||||
file_paths = Settings().value(self.settings_section + '/presentations files')
|
file_paths = Settings().value(self.settings_section + '/presentations files')
|
||||||
self.load_list([path_to_str(file) for file in file_paths], initial_load=True)
|
self.load_list([path_to_str(path) for path in file_paths], initial_load=True)
|
||||||
self.populate_display_types()
|
self.populate_display_types()
|
||||||
|
|
||||||
def populate_display_types(self):
|
def populate_display_types(self):
|
||||||
|
@ -152,54 +150,57 @@ class PresentationMediaItem(MediaManagerItem):
|
||||||
else:
|
else:
|
||||||
self.presentation_widget.hide()
|
self.presentation_widget.hide()
|
||||||
|
|
||||||
def load_list(self, files, target_group=None, initial_load=False):
|
def load_list(self, file_paths, target_group=None, initial_load=False):
|
||||||
"""
|
"""
|
||||||
Add presentations into the media manager. This is called both on initial load of the plugin to populate with
|
Add presentations into the media manager. This is called both on initial load of the plugin to populate with
|
||||||
existing files, and when the user adds new files via the media manager.
|
existing files, and when the user adds new files via the media manager.
|
||||||
|
|
||||||
|
:param list[openlp.core.common.path.Path] file_paths: List of file paths to add to the media manager.
|
||||||
"""
|
"""
|
||||||
current_list = self.get_file_list()
|
file_paths = [str_to_path(filename) for filename in file_paths]
|
||||||
titles = [file_path.name for file_path in current_list]
|
current_paths = self.get_file_list()
|
||||||
|
titles = [file_path.name for file_path in current_paths]
|
||||||
self.application.set_busy_cursor()
|
self.application.set_busy_cursor()
|
||||||
if not initial_load:
|
if not initial_load:
|
||||||
self.main_window.display_progress_bar(len(files))
|
self.main_window.display_progress_bar(len(file_paths))
|
||||||
# Sort the presentations by its filename considering language specific characters.
|
# Sort the presentations by its filename considering language specific characters.
|
||||||
files.sort(key=lambda filename: get_locale_key(os.path.split(str(filename))[1]))
|
file_paths.sort(key=lambda file_path: get_locale_key(file_path.name))
|
||||||
for file in files:
|
for file_path in file_paths:
|
||||||
if not initial_load:
|
if not initial_load:
|
||||||
self.main_window.increment_progress_bar()
|
self.main_window.increment_progress_bar()
|
||||||
if current_list.count(file) > 0:
|
if current_paths.count(file_path) > 0:
|
||||||
continue
|
continue
|
||||||
filename = os.path.split(file)[1]
|
file_name = file_path.name
|
||||||
if not os.path.exists(file):
|
if not file_path.exists():
|
||||||
item_name = QtWidgets.QListWidgetItem(filename)
|
item_name = QtWidgets.QListWidgetItem(file_name)
|
||||||
item_name.setIcon(build_icon(ERROR_IMAGE))
|
item_name.setIcon(build_icon(ERROR_IMAGE))
|
||||||
item_name.setData(QtCore.Qt.UserRole, file)
|
item_name.setData(QtCore.Qt.UserRole, path_to_str(file_path))
|
||||||
item_name.setToolTip(file)
|
item_name.setToolTip(str(file_path))
|
||||||
self.list_view.addItem(item_name)
|
self.list_view.addItem(item_name)
|
||||||
else:
|
else:
|
||||||
if titles.count(filename) > 0:
|
if titles.count(file_name) > 0:
|
||||||
if not initial_load:
|
if not initial_load:
|
||||||
critical_error_message_box(translate('PresentationPlugin.MediaItem', 'File Exists'),
|
critical_error_message_box(translate('PresentationPlugin.MediaItem', 'File Exists'),
|
||||||
translate('PresentationPlugin.MediaItem',
|
translate('PresentationPlugin.MediaItem',
|
||||||
'A presentation with that filename already exists.'))
|
'A presentation with that filename already exists.'))
|
||||||
continue
|
continue
|
||||||
controller_name = self.find_controller_by_type(filename)
|
controller_name = self.find_controller_by_type(file_path)
|
||||||
if controller_name:
|
if controller_name:
|
||||||
controller = self.controllers[controller_name]
|
controller = self.controllers[controller_name]
|
||||||
doc = controller.add_document(file)
|
doc = controller.add_document(file_path)
|
||||||
thumb = os.path.join(doc.get_thumbnail_folder(), 'icon.png')
|
thumbnail_path = doc.get_thumbnail_folder() / 'icon.png'
|
||||||
preview = doc.get_thumbnail_path(1, True)
|
preview_path = doc.get_thumbnail_path(1, True)
|
||||||
if not preview and not initial_load:
|
if not preview_path and not initial_load:
|
||||||
doc.load_presentation()
|
doc.load_presentation()
|
||||||
preview = doc.get_thumbnail_path(1, True)
|
preview_path = doc.get_thumbnail_path(1, True)
|
||||||
doc.close_presentation()
|
doc.close_presentation()
|
||||||
if not (preview and os.path.exists(preview)):
|
if not (preview_path and preview_path.exists()):
|
||||||
icon = build_icon(':/general/general_delete.png')
|
icon = build_icon(':/general/general_delete.png')
|
||||||
else:
|
else:
|
||||||
if validate_thumb(preview, thumb):
|
if validate_thumb(Path(preview_path), Path(thumbnail_path)):
|
||||||
icon = build_icon(thumb)
|
icon = build_icon(thumbnail_path)
|
||||||
else:
|
else:
|
||||||
icon = create_thumb(preview, thumb)
|
icon = create_thumb(str(preview_path), str(thumbnail_path))
|
||||||
else:
|
else:
|
||||||
if initial_load:
|
if initial_load:
|
||||||
icon = build_icon(':/general/general_delete.png')
|
icon = build_icon(':/general/general_delete.png')
|
||||||
|
@ -208,10 +209,10 @@ class PresentationMediaItem(MediaManagerItem):
|
||||||
translate('PresentationPlugin.MediaItem',
|
translate('PresentationPlugin.MediaItem',
|
||||||
'This type of presentation is not supported.'))
|
'This type of presentation is not supported.'))
|
||||||
continue
|
continue
|
||||||
item_name = QtWidgets.QListWidgetItem(filename)
|
item_name = QtWidgets.QListWidgetItem(file_name)
|
||||||
item_name.setData(QtCore.Qt.UserRole, file)
|
item_name.setData(QtCore.Qt.UserRole, path_to_str(file_path))
|
||||||
item_name.setIcon(icon)
|
item_name.setIcon(icon)
|
||||||
item_name.setToolTip(file)
|
item_name.setToolTip(str(file_path))
|
||||||
self.list_view.addItem(item_name)
|
self.list_view.addItem(item_name)
|
||||||
if not initial_load:
|
if not initial_load:
|
||||||
self.main_window.finished_progress_bar()
|
self.main_window.finished_progress_bar()
|
||||||
|
@ -228,8 +229,8 @@ class PresentationMediaItem(MediaManagerItem):
|
||||||
self.application.set_busy_cursor()
|
self.application.set_busy_cursor()
|
||||||
self.main_window.display_progress_bar(len(row_list))
|
self.main_window.display_progress_bar(len(row_list))
|
||||||
for item in items:
|
for item in items:
|
||||||
filepath = str(item.data(QtCore.Qt.UserRole))
|
file_path = str_to_path(item.data(QtCore.Qt.UserRole))
|
||||||
self.clean_up_thumbnails(filepath)
|
self.clean_up_thumbnails(file_path)
|
||||||
self.main_window.increment_progress_bar()
|
self.main_window.increment_progress_bar()
|
||||||
self.main_window.finished_progress_bar()
|
self.main_window.finished_progress_bar()
|
||||||
for row in row_list:
|
for row in row_list:
|
||||||
|
@ -237,30 +238,29 @@ class PresentationMediaItem(MediaManagerItem):
|
||||||
Settings().setValue(self.settings_section + '/presentations files', self.get_file_list())
|
Settings().setValue(self.settings_section + '/presentations files', self.get_file_list())
|
||||||
self.application.set_normal_cursor()
|
self.application.set_normal_cursor()
|
||||||
|
|
||||||
def clean_up_thumbnails(self, filepath, clean_for_update=False):
|
def clean_up_thumbnails(self, file_path, clean_for_update=False):
|
||||||
"""
|
"""
|
||||||
Clean up the files created such as thumbnails
|
Clean up the files created such as thumbnails
|
||||||
|
|
||||||
:param filepath: File path of the presention to clean up after
|
:param openlp.core.common.path.Path file_path: File path of the presention to clean up after
|
||||||
:param clean_for_update: Only clean thumbnails if update is needed
|
:param bool clean_for_update: Only clean thumbnails if update is needed
|
||||||
:return: None
|
:rtype: None
|
||||||
"""
|
"""
|
||||||
for cidx in self.controllers:
|
for cidx in self.controllers:
|
||||||
root, file_ext = os.path.splitext(filepath)
|
file_ext = file_path.suffix[1:]
|
||||||
file_ext = file_ext[1:]
|
|
||||||
if file_ext in self.controllers[cidx].supports or file_ext in self.controllers[cidx].also_supports:
|
if file_ext in self.controllers[cidx].supports or file_ext in self.controllers[cidx].also_supports:
|
||||||
doc = self.controllers[cidx].add_document(filepath)
|
doc = self.controllers[cidx].add_document(file_path)
|
||||||
if clean_for_update:
|
if clean_for_update:
|
||||||
thumb_path = doc.get_thumbnail_path(1, True)
|
thumb_path = doc.get_thumbnail_path(1, True)
|
||||||
if not thumb_path or not os.path.exists(filepath) or os.path.getmtime(
|
if not thumb_path or not file_path.exists() or \
|
||||||
thumb_path) < os.path.getmtime(filepath):
|
thumb_path.stat().st_mtime < file_path.stat().st_mtime:
|
||||||
doc.presentation_deleted()
|
doc.presentation_deleted()
|
||||||
else:
|
else:
|
||||||
doc.presentation_deleted()
|
doc.presentation_deleted()
|
||||||
doc.close_presentation()
|
doc.close_presentation()
|
||||||
|
|
||||||
def generate_slide_data(self, service_item, item=None, xml_version=False, remote=False,
|
def generate_slide_data(self, service_item, item=None, xml_version=False, remote=False,
|
||||||
context=ServiceItemContext.Service, presentation_file=None):
|
context=ServiceItemContext.Service, file_path=None):
|
||||||
"""
|
"""
|
||||||
Generate the slide data. Needs to be implemented by the plugin.
|
Generate the slide data. Needs to be implemented by the plugin.
|
||||||
|
|
||||||
|
@ -276,10 +276,9 @@ class PresentationMediaItem(MediaManagerItem):
|
||||||
items = self.list_view.selectedItems()
|
items = self.list_view.selectedItems()
|
||||||
if len(items) > 1:
|
if len(items) > 1:
|
||||||
return False
|
return False
|
||||||
filename = presentation_file
|
if file_path is None:
|
||||||
if filename is None:
|
file_path = str_to_path(items[0].data(QtCore.Qt.UserRole))
|
||||||
filename = items[0].data(QtCore.Qt.UserRole)
|
file_type = file_path.suffix.lower()[1:]
|
||||||
file_type = os.path.splitext(filename.lower())[1][1:]
|
|
||||||
if not self.display_type_combo_box.currentText():
|
if not self.display_type_combo_box.currentText():
|
||||||
return False
|
return False
|
||||||
service_item.add_capability(ItemCapabilities.CanEditTitle)
|
service_item.add_capability(ItemCapabilities.CanEditTitle)
|
||||||
|
@ -292,29 +291,28 @@ class PresentationMediaItem(MediaManagerItem):
|
||||||
# force a nonexistent theme
|
# force a nonexistent theme
|
||||||
service_item.theme = -1
|
service_item.theme = -1
|
||||||
for bitem in items:
|
for bitem in items:
|
||||||
filename = presentation_file
|
if file_path is None:
|
||||||
if filename is None:
|
file_path = str_to_path(bitem.data(QtCore.Qt.UserRole))
|
||||||
filename = bitem.data(QtCore.Qt.UserRole)
|
path, file_name = file_path.parent, file_path.name
|
||||||
(path, name) = os.path.split(filename)
|
service_item.title = file_name
|
||||||
service_item.title = name
|
if file_path.exists():
|
||||||
if os.path.exists(filename):
|
processor = self.find_controller_by_type(file_path)
|
||||||
processor = self.find_controller_by_type(filename)
|
|
||||||
if not processor:
|
if not processor:
|
||||||
return False
|
return False
|
||||||
controller = self.controllers[processor]
|
controller = self.controllers[processor]
|
||||||
service_item.processor = None
|
service_item.processor = None
|
||||||
doc = controller.add_document(filename)
|
doc = controller.add_document(file_path)
|
||||||
if doc.get_thumbnail_path(1, True) is None or not os.path.isfile(
|
if doc.get_thumbnail_path(1, True) is None or \
|
||||||
os.path.join(doc.get_temp_folder(), 'mainslide001.png')):
|
not (doc.get_temp_folder() / 'mainslide001.png').is_file():
|
||||||
doc.load_presentation()
|
doc.load_presentation()
|
||||||
i = 1
|
i = 1
|
||||||
image = os.path.join(doc.get_temp_folder(), 'mainslide{number:0>3d}.png'.format(number=i))
|
image_path = doc.get_temp_folder() / 'mainslide{number:0>3d}.png'.format(number=i)
|
||||||
thumbnail = os.path.join(doc.get_thumbnail_folder(), 'slide%d.png' % i)
|
thumbnail_path = doc.get_thumbnail_folder() / 'slide{number:d}.png'.format(number=i)
|
||||||
while os.path.isfile(image):
|
while image_path.is_file():
|
||||||
service_item.add_from_image(image, name, thumbnail=thumbnail)
|
service_item.add_from_image(str(image_path), file_name, thumbnail=str(thumbnail_path))
|
||||||
i += 1
|
i += 1
|
||||||
image = os.path.join(doc.get_temp_folder(), 'mainslide{number:0>3d}.png'.format(number=i))
|
image_path = doc.get_temp_folder() / 'mainslide{number:0>3d}.png'.format(number=i)
|
||||||
thumbnail = os.path.join(doc.get_thumbnail_folder(), 'slide{number:d}.png'.format(number=i))
|
thumbnail_path = doc.get_thumbnail_folder() / 'slide{number:d}.png'.format(number=i)
|
||||||
service_item.add_capability(ItemCapabilities.HasThumbnails)
|
service_item.add_capability(ItemCapabilities.HasThumbnails)
|
||||||
doc.close_presentation()
|
doc.close_presentation()
|
||||||
return True
|
return True
|
||||||
|
@ -324,34 +322,34 @@ class PresentationMediaItem(MediaManagerItem):
|
||||||
critical_error_message_box(translate('PresentationPlugin.MediaItem', 'Missing Presentation'),
|
critical_error_message_box(translate('PresentationPlugin.MediaItem', 'Missing Presentation'),
|
||||||
translate('PresentationPlugin.MediaItem',
|
translate('PresentationPlugin.MediaItem',
|
||||||
'The presentation {name} no longer exists.'
|
'The presentation {name} no longer exists.'
|
||||||
).format(name=filename))
|
).format(name=file_path))
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
service_item.processor = self.display_type_combo_box.currentText()
|
service_item.processor = self.display_type_combo_box.currentText()
|
||||||
service_item.add_capability(ItemCapabilities.ProvidesOwnDisplay)
|
service_item.add_capability(ItemCapabilities.ProvidesOwnDisplay)
|
||||||
for bitem in items:
|
for bitem in items:
|
||||||
filename = bitem.data(QtCore.Qt.UserRole)
|
file_path = str_to_path(bitem.data(QtCore.Qt.UserRole))
|
||||||
(path, name) = os.path.split(filename)
|
path, file_name = file_path.parent, file_path.name
|
||||||
service_item.title = name
|
service_item.title = file_name
|
||||||
if os.path.exists(filename):
|
if file_path.exists():
|
||||||
if self.display_type_combo_box.itemData(self.display_type_combo_box.currentIndex()) == 'automatic':
|
if self.display_type_combo_box.itemData(self.display_type_combo_box.currentIndex()) == 'automatic':
|
||||||
service_item.processor = self.find_controller_by_type(filename)
|
service_item.processor = self.find_controller_by_type(file_path)
|
||||||
if not service_item.processor:
|
if not service_item.processor:
|
||||||
return False
|
return False
|
||||||
controller = self.controllers[service_item.processor]
|
controller = self.controllers[service_item.processor]
|
||||||
doc = controller.add_document(filename)
|
doc = controller.add_document(file_path)
|
||||||
if doc.get_thumbnail_path(1, True) is None:
|
if doc.get_thumbnail_path(1, True) is None:
|
||||||
doc.load_presentation()
|
doc.load_presentation()
|
||||||
i = 1
|
i = 1
|
||||||
img = doc.get_thumbnail_path(i, True)
|
thumbnail_path = doc.get_thumbnail_path(i, True)
|
||||||
if img:
|
if thumbnail_path:
|
||||||
# Get titles and notes
|
# Get titles and notes
|
||||||
titles, notes = doc.get_titles_and_notes()
|
titles, notes = doc.get_titles_and_notes()
|
||||||
service_item.add_capability(ItemCapabilities.HasDisplayTitle)
|
service_item.add_capability(ItemCapabilities.HasDisplayTitle)
|
||||||
if notes.count('') != len(notes):
|
if notes.count('') != len(notes):
|
||||||
service_item.add_capability(ItemCapabilities.HasNotes)
|
service_item.add_capability(ItemCapabilities.HasNotes)
|
||||||
service_item.add_capability(ItemCapabilities.HasThumbnails)
|
service_item.add_capability(ItemCapabilities.HasThumbnails)
|
||||||
while img:
|
while thumbnail_path:
|
||||||
# Use title and note if available
|
# Use title and note if available
|
||||||
title = ''
|
title = ''
|
||||||
if titles and len(titles) >= i:
|
if titles and len(titles) >= i:
|
||||||
|
@ -359,9 +357,9 @@ class PresentationMediaItem(MediaManagerItem):
|
||||||
note = ''
|
note = ''
|
||||||
if notes and len(notes) >= i:
|
if notes and len(notes) >= i:
|
||||||
note = notes[i - 1]
|
note = notes[i - 1]
|
||||||
service_item.add_from_command(path, name, img, title, note)
|
service_item.add_from_command(str(path), file_name, str(thumbnail_path), title, note)
|
||||||
i += 1
|
i += 1
|
||||||
img = doc.get_thumbnail_path(i, True)
|
thumbnail_path = doc.get_thumbnail_path(i, True)
|
||||||
doc.close_presentation()
|
doc.close_presentation()
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
|
@ -371,7 +369,7 @@ class PresentationMediaItem(MediaManagerItem):
|
||||||
'Missing Presentation'),
|
'Missing Presentation'),
|
||||||
translate('PresentationPlugin.MediaItem',
|
translate('PresentationPlugin.MediaItem',
|
||||||
'The presentation {name} is incomplete, '
|
'The presentation {name} is incomplete, '
|
||||||
'please reload.').format(name=filename))
|
'please reload.').format(name=file_path))
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
# File is no longer present
|
# File is no longer present
|
||||||
|
@ -379,18 +377,20 @@ class PresentationMediaItem(MediaManagerItem):
|
||||||
critical_error_message_box(translate('PresentationPlugin.MediaItem', 'Missing Presentation'),
|
critical_error_message_box(translate('PresentationPlugin.MediaItem', 'Missing Presentation'),
|
||||||
translate('PresentationPlugin.MediaItem',
|
translate('PresentationPlugin.MediaItem',
|
||||||
'The presentation {name} no longer exists.'
|
'The presentation {name} no longer exists.'
|
||||||
).format(name=filename))
|
).format(name=file_path))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def find_controller_by_type(self, filename):
|
def find_controller_by_type(self, file_path):
|
||||||
"""
|
"""
|
||||||
Determine the default application controller to use for the selected file type. This is used if "Automatic" is
|
Determine the default application controller to use for the selected file type. This is used if "Automatic" is
|
||||||
set as the preferred controller. Find the first (alphabetic) enabled controller which "supports" the extension.
|
set as the preferred controller. Find the first (alphabetic) enabled controller which "supports" the extension.
|
||||||
If none found, then look for a controller which "also supports" it instead.
|
If none found, then look for a controller which "also supports" it instead.
|
||||||
|
|
||||||
:param filename: The file name
|
:param openlp.core.common.path.Path file_path: The file path
|
||||||
|
:return: The default application controller for this file type, or None if not supported
|
||||||
|
:rtype: PresentationController
|
||||||
"""
|
"""
|
||||||
file_type = os.path.splitext(filename)[1][1:]
|
file_type = file_path.suffix[1:]
|
||||||
if not file_type:
|
if not file_type:
|
||||||
return None
|
return None
|
||||||
for controller in self.controllers:
|
for controller in self.controllers:
|
||||||
|
|
|
@ -19,16 +19,15 @@
|
||||||
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
import logging
|
|
||||||
import copy
|
import copy
|
||||||
import os
|
import logging
|
||||||
|
|
||||||
from PyQt5 import QtCore
|
from PyQt5 import QtCore
|
||||||
|
|
||||||
from openlp.core.common import Registry, Settings
|
from openlp.core.common import Registry, Settings
|
||||||
from openlp.core.ui import HideMode
|
from openlp.core.common.path import Path
|
||||||
from openlp.core.lib import ServiceItemContext
|
from openlp.core.lib import ServiceItemContext
|
||||||
|
from openlp.core.ui import HideMode
|
||||||
from openlp.plugins.presentations.lib.pdfcontroller import PDF_CONTROLLER_FILETYPES
|
from openlp.plugins.presentations.lib.pdfcontroller import PDF_CONTROLLER_FILETYPES
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
@ -325,21 +324,25 @@ class MessageListener(object):
|
||||||
is_live = message[1]
|
is_live = message[1]
|
||||||
item = message[0]
|
item = message[0]
|
||||||
hide_mode = message[2]
|
hide_mode = message[2]
|
||||||
file = item.get_frame_path()
|
file_path = Path(item.get_frame_path())
|
||||||
self.handler = item.processor
|
self.handler = item.processor
|
||||||
# When starting presentation from the servicemanager we convert
|
# When starting presentation from the servicemanager we convert
|
||||||
# PDF/XPS/OXPS-serviceitems into image-serviceitems. When started from the mediamanager
|
# PDF/XPS/OXPS-serviceitems into image-serviceitems. When started from the mediamanager
|
||||||
# the conversion has already been done at this point.
|
# the conversion has already been done at this point.
|
||||||
file_type = os.path.splitext(file.lower())[1][1:]
|
file_type = file_path.suffix.lower()[1:]
|
||||||
if file_type in PDF_CONTROLLER_FILETYPES:
|
if file_type in PDF_CONTROLLER_FILETYPES:
|
||||||
log.debug('Converting from pdf/xps/oxps to images for serviceitem with file {name}'.format(name=file))
|
log.debug('Converting from pdf/xps/oxps to images for serviceitem with file {name}'.format(name=file_path))
|
||||||
# Create a copy of the original item, and then clear the original item so it can be filled with images
|
# Create a copy of the original item, and then clear the original item so it can be filled with images
|
||||||
item_cpy = copy.copy(item)
|
item_cpy = copy.copy(item)
|
||||||
item.__init__(None)
|
item.__init__(None)
|
||||||
if is_live:
|
if is_live:
|
||||||
self.media_item.generate_slide_data(item, item_cpy, False, False, ServiceItemContext.Live, file)
|
# TODO: To Path object
|
||||||
|
self.media_item.generate_slide_data(item, item_cpy, False, False, ServiceItemContext.Live,
|
||||||
|
str(file_path))
|
||||||
else:
|
else:
|
||||||
self.media_item.generate_slide_data(item, item_cpy, False, False, ServiceItemContext.Preview, file)
|
# TODO: To Path object
|
||||||
|
self.media_item.generate_slide_data(item, item_cpy, False, False, ServiceItemContext.Preview,
|
||||||
|
str(file_path))
|
||||||
# Some of the original serviceitem attributes is needed in the new serviceitem
|
# Some of the original serviceitem attributes is needed in the new serviceitem
|
||||||
item.footer = item_cpy.footer
|
item.footer = item_cpy.footer
|
||||||
item.from_service = item_cpy.from_service
|
item.from_service = item_cpy.from_service
|
||||||
|
@ -352,13 +355,13 @@ class MessageListener(object):
|
||||||
self.handler = None
|
self.handler = None
|
||||||
else:
|
else:
|
||||||
if self.handler == self.media_item.automatic:
|
if self.handler == self.media_item.automatic:
|
||||||
self.handler = self.media_item.find_controller_by_type(file)
|
self.handler = self.media_item.find_controller_by_type(file_path)
|
||||||
if not self.handler:
|
if not self.handler:
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
# the saved handler is not present so need to use one based on file suffix.
|
# the saved handler is not present so need to use one based on file_path suffix.
|
||||||
if not self.controllers[self.handler].available:
|
if not self.controllers[self.handler].available:
|
||||||
self.handler = self.media_item.find_controller_by_type(file)
|
self.handler = self.media_item.find_controller_by_type(file_path)
|
||||||
if not self.handler:
|
if not self.handler:
|
||||||
return
|
return
|
||||||
if is_live:
|
if is_live:
|
||||||
|
@ -370,7 +373,7 @@ class MessageListener(object):
|
||||||
if self.handler is None:
|
if self.handler is None:
|
||||||
self.controller = controller
|
self.controller = controller
|
||||||
else:
|
else:
|
||||||
controller.add_handler(self.controllers[self.handler], file, hide_mode, message[3])
|
controller.add_handler(self.controllers[self.handler], file_path, hide_mode, message[3])
|
||||||
self.timer.start()
|
self.timer.start()
|
||||||
|
|
||||||
def slide(self, message):
|
def slide(self, message):
|
||||||
|
|
|
@ -23,12 +23,11 @@
|
||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
from shutil import which
|
|
||||||
from subprocess import check_output, CalledProcessError
|
from subprocess import check_output, CalledProcessError
|
||||||
|
|
||||||
from openlp.core.common import AppLocation, check_binary_exists
|
from openlp.core.common import AppLocation, check_binary_exists
|
||||||
from openlp.core.common import Settings, is_win
|
from openlp.core.common import Settings, is_win
|
||||||
from openlp.core.common.path import Path, path_to_str
|
from openlp.core.common.path import which
|
||||||
from openlp.core.lib import ScreenList
|
from openlp.core.lib import ScreenList
|
||||||
from openlp.plugins.presentations.lib.presentationcontroller import PresentationController, PresentationDocument
|
from openlp.plugins.presentations.lib.presentationcontroller import PresentationController, PresentationDocument
|
||||||
|
|
||||||
|
@ -66,11 +65,12 @@ class PdfController(PresentationController):
|
||||||
Function that checks whether a binary is either ghostscript or mudraw or neither.
|
Function that checks whether a binary is either ghostscript or mudraw or neither.
|
||||||
Is also used from presentationtab.py
|
Is also used from presentationtab.py
|
||||||
|
|
||||||
:param program_path:The full path to the binary to check.
|
:param openlp.core.common.path.Path program_path: The full path to the binary to check.
|
||||||
:return: Type of the binary, 'gs' if ghostscript, 'mudraw' if mudraw, None if invalid.
|
:return: Type of the binary, 'gs' if ghostscript, 'mudraw' if mudraw, None if invalid.
|
||||||
|
:rtype: str | None
|
||||||
"""
|
"""
|
||||||
program_type = None
|
program_type = None
|
||||||
runlog = check_binary_exists(Path(program_path))
|
runlog = check_binary_exists(program_path)
|
||||||
# Analyse the output to see it the program is mudraw, ghostscript or neither
|
# Analyse the output to see it the program is mudraw, ghostscript or neither
|
||||||
for line in runlog.splitlines():
|
for line in runlog.splitlines():
|
||||||
decoded_line = line.decode()
|
decoded_line = line.decode()
|
||||||
|
@ -107,30 +107,29 @@ class PdfController(PresentationController):
|
||||||
:return: True if program to open PDF-files was found, otherwise False.
|
:return: True if program to open PDF-files was found, otherwise False.
|
||||||
"""
|
"""
|
||||||
log.debug('check_installed Pdf')
|
log.debug('check_installed Pdf')
|
||||||
self.mudrawbin = ''
|
self.mudrawbin = None
|
||||||
self.mutoolbin = ''
|
self.mutoolbin = None
|
||||||
self.gsbin = ''
|
self.gsbin = None
|
||||||
self.also_supports = []
|
self.also_supports = []
|
||||||
# Use the user defined program if given
|
# Use the user defined program if given
|
||||||
if Settings().value('presentations/enable_pdf_program'):
|
if Settings().value('presentations/enable_pdf_program'):
|
||||||
pdf_program = path_to_str(Settings().value('presentations/pdf_program'))
|
program_path = Settings().value('presentations/pdf_program')
|
||||||
program_type = self.process_check_binary(pdf_program)
|
program_type = self.process_check_binary(program_path)
|
||||||
if program_type == 'gs':
|
if program_type == 'gs':
|
||||||
self.gsbin = pdf_program
|
self.gsbin = program_path
|
||||||
elif program_type == 'mudraw':
|
elif program_type == 'mudraw':
|
||||||
self.mudrawbin = pdf_program
|
self.mudrawbin = program_path
|
||||||
elif program_type == 'mutool':
|
elif program_type == 'mutool':
|
||||||
self.mutoolbin = pdf_program
|
self.mutoolbin = program_path
|
||||||
else:
|
else:
|
||||||
# Fallback to autodetection
|
# Fallback to autodetection
|
||||||
application_path = str(AppLocation.get_directory(AppLocation.AppDir))
|
application_path = AppLocation.get_directory(AppLocation.AppDir)
|
||||||
if is_win():
|
if is_win():
|
||||||
# for windows we only accept mudraw.exe or mutool.exe in the base folder
|
# for windows we only accept mudraw.exe or mutool.exe in the base folder
|
||||||
application_path = str(AppLocation.get_directory(AppLocation.AppDir))
|
if (application_path / 'mudraw.exe').is_file():
|
||||||
if os.path.isfile(os.path.join(application_path, 'mudraw.exe')):
|
self.mudrawbin = application_path / 'mudraw.exe'
|
||||||
self.mudrawbin = os.path.join(application_path, 'mudraw.exe')
|
elif (application_path / 'mutool.exe').is_file():
|
||||||
elif os.path.isfile(os.path.join(application_path, 'mutool.exe')):
|
self.mutoolbin = application_path / 'mutool.exe'
|
||||||
self.mutoolbin = os.path.join(application_path, 'mutool.exe')
|
|
||||||
else:
|
else:
|
||||||
DEVNULL = open(os.devnull, 'wb')
|
DEVNULL = open(os.devnull, 'wb')
|
||||||
# First try to find mudraw
|
# First try to find mudraw
|
||||||
|
@ -143,11 +142,11 @@ class PdfController(PresentationController):
|
||||||
self.gsbin = which('gs')
|
self.gsbin = which('gs')
|
||||||
# Last option: check if mudraw or mutool is placed in OpenLP base folder
|
# 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:
|
if not self.mudrawbin and not self.mutoolbin and not self.gsbin:
|
||||||
application_path = str(AppLocation.get_directory(AppLocation.AppDir))
|
application_path = AppLocation.get_directory(AppLocation.AppDir)
|
||||||
if os.path.isfile(os.path.join(application_path, 'mudraw')):
|
if (application_path / 'mudraw').is_file():
|
||||||
self.mudrawbin = os.path.join(application_path, 'mudraw')
|
self.mudrawbin = application_path / 'mudraw'
|
||||||
elif os.path.isfile(os.path.join(application_path, 'mutool')):
|
elif (application_path / 'mutool').is_file():
|
||||||
self.mutoolbin = os.path.join(application_path, 'mutool')
|
self.mutoolbin = application_path / 'mutool'
|
||||||
if self.mudrawbin or self.mutoolbin:
|
if self.mudrawbin or self.mutoolbin:
|
||||||
self.also_supports = ['xps', 'oxps']
|
self.also_supports = ['xps', 'oxps']
|
||||||
return True
|
return True
|
||||||
|
@ -172,12 +171,15 @@ class PdfDocument(PresentationDocument):
|
||||||
image-serviceitem on the fly and present as such. Therefore some of the 'playback'
|
image-serviceitem on the fly and present as such. Therefore some of the 'playback'
|
||||||
functions is not implemented.
|
functions is not implemented.
|
||||||
"""
|
"""
|
||||||
def __init__(self, controller, presentation):
|
def __init__(self, controller, document_path):
|
||||||
"""
|
"""
|
||||||
Constructor, store information about the file and initialise.
|
Constructor, store information about the file and initialise.
|
||||||
|
|
||||||
|
:param openlp.core.common.path.Path document_path: Path to the document to load
|
||||||
|
:rtype: None
|
||||||
"""
|
"""
|
||||||
log.debug('Init Presentation Pdf')
|
log.debug('Init Presentation Pdf')
|
||||||
PresentationDocument.__init__(self, controller, presentation)
|
super().__init__(controller, document_path)
|
||||||
self.presentation = None
|
self.presentation = None
|
||||||
self.blanked = False
|
self.blanked = False
|
||||||
self.hidden = False
|
self.hidden = False
|
||||||
|
@ -200,13 +202,13 @@ class PdfDocument(PresentationDocument):
|
||||||
:return: The resolution dpi to be used.
|
: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
|
# Use a postscript script to get size of the pdf. It is assumed that all pages have same size
|
||||||
gs_resolution_script = str(AppLocation.get_directory(
|
gs_resolution_script = AppLocation.get_directory(
|
||||||
AppLocation.PluginsDir)) + '/presentations/lib/ghostscript_get_resolution.ps'
|
AppLocation.PluginsDir) / 'presentations' / 'lib' / 'ghostscript_get_resolution.ps'
|
||||||
# Run the script on the pdf to get the size
|
# Run the script on the pdf to get the size
|
||||||
runlog = []
|
runlog = []
|
||||||
try:
|
try:
|
||||||
runlog = check_output([self.controller.gsbin, '-dNOPAUSE', '-dNODISPLAY', '-dBATCH',
|
runlog = check_output([str(self.controller.gsbin), '-dNOPAUSE', '-dNODISPLAY', '-dBATCH',
|
||||||
'-sFile=' + self.file_path, gs_resolution_script],
|
'-sFile={file_path}'.format(file_path=self.file_path), str(gs_resolution_script)],
|
||||||
startupinfo=self.startupinfo)
|
startupinfo=self.startupinfo)
|
||||||
except CalledProcessError as e:
|
except CalledProcessError as e:
|
||||||
log.debug(' '.join(e.cmd))
|
log.debug(' '.join(e.cmd))
|
||||||
|
@ -240,46 +242,47 @@ class PdfDocument(PresentationDocument):
|
||||||
:return: True is loading succeeded, otherwise False.
|
:return: True is loading succeeded, otherwise False.
|
||||||
"""
|
"""
|
||||||
log.debug('load_presentation pdf')
|
log.debug('load_presentation pdf')
|
||||||
|
temp_dir_path = self.get_temp_folder()
|
||||||
# Check if the images has already been created, and if yes load them
|
# Check if the images has already been created, and if yes load them
|
||||||
if os.path.isfile(os.path.join(self.get_temp_folder(), 'mainslide001.png')):
|
if (temp_dir_path / 'mainslide001.png').is_file():
|
||||||
created_files = sorted(os.listdir(self.get_temp_folder()))
|
created_files = sorted(temp_dir_path.glob('*'))
|
||||||
for fn in created_files:
|
for image_path in created_files:
|
||||||
if os.path.isfile(os.path.join(self.get_temp_folder(), fn)):
|
if image_path.is_file():
|
||||||
self.image_files.append(os.path.join(self.get_temp_folder(), fn))
|
self.image_files.append(image_path)
|
||||||
self.num_pages = len(self.image_files)
|
self.num_pages = len(self.image_files)
|
||||||
return True
|
return True
|
||||||
size = ScreenList().current['size']
|
size = ScreenList().current['size']
|
||||||
# Generate images from PDF that will fit the frame.
|
# Generate images from PDF that will fit the frame.
|
||||||
runlog = ''
|
runlog = ''
|
||||||
try:
|
try:
|
||||||
if not os.path.isdir(self.get_temp_folder()):
|
if not temp_dir_path.is_dir():
|
||||||
os.makedirs(self.get_temp_folder())
|
temp_dir_path.mkdir(parents=True)
|
||||||
# The %03d in the file name is handled by each binary
|
# The %03d in the file name is handled by each binary
|
||||||
if self.controller.mudrawbin:
|
if self.controller.mudrawbin:
|
||||||
log.debug('loading presentation using mudraw')
|
log.debug('loading presentation using mudraw')
|
||||||
runlog = check_output([self.controller.mudrawbin, '-w', str(size.width()), '-h', str(size.height()),
|
runlog = check_output([str(self.controller.mudrawbin), '-w', str(size.width()),
|
||||||
'-o', os.path.join(self.get_temp_folder(), 'mainslide%03d.png'), self.file_path],
|
'-h', str(size.height()),
|
||||||
|
'-o', str(temp_dir_path / 'mainslide%03d.png'), str(self.file_path)],
|
||||||
startupinfo=self.startupinfo)
|
startupinfo=self.startupinfo)
|
||||||
elif self.controller.mutoolbin:
|
elif self.controller.mutoolbin:
|
||||||
log.debug('loading presentation using mutool')
|
log.debug('loading presentation using mutool')
|
||||||
runlog = check_output([self.controller.mutoolbin, 'draw', '-w', str(size.width()), '-h',
|
runlog = check_output([str(self.controller.mutoolbin), 'draw', '-w', str(size.width()),
|
||||||
str(size.height()),
|
'-h', str(size.height()), '-o', str(temp_dir_path / 'mainslide%03d.png'),
|
||||||
'-o', os.path.join(self.get_temp_folder(), 'mainslide%03d.png'), self.file_path],
|
str(self.file_path)],
|
||||||
startupinfo=self.startupinfo)
|
startupinfo=self.startupinfo)
|
||||||
elif self.controller.gsbin:
|
elif self.controller.gsbin:
|
||||||
log.debug('loading presentation using gs')
|
log.debug('loading presentation using gs')
|
||||||
resolution = self.gs_get_resolution(size)
|
resolution = self.gs_get_resolution(size)
|
||||||
runlog = check_output([self.controller.gsbin, '-dSAFER', '-dNOPAUSE', '-dBATCH', '-sDEVICE=png16m',
|
runlog = check_output([str(self.controller.gsbin), '-dSAFER', '-dNOPAUSE', '-dBATCH', '-sDEVICE=png16m',
|
||||||
'-r' + str(resolution), '-dTextAlphaBits=4', '-dGraphicsAlphaBits=4',
|
'-r{res}'.format(res=resolution), '-dTextAlphaBits=4', '-dGraphicsAlphaBits=4',
|
||||||
'-sOutputFile=' + os.path.join(self.get_temp_folder(), 'mainslide%03d.png'),
|
'-sOutputFile={output}'.format(output=temp_dir_path / 'mainslide%03d.png'),
|
||||||
self.file_path], startupinfo=self.startupinfo)
|
str(self.file_path)], startupinfo=self.startupinfo)
|
||||||
created_files = sorted(os.listdir(self.get_temp_folder()))
|
created_files = sorted(temp_dir_path.glob('*'))
|
||||||
for fn in created_files:
|
for image_path in created_files:
|
||||||
if os.path.isfile(os.path.join(self.get_temp_folder(), fn)):
|
if image_path.is_file():
|
||||||
self.image_files.append(os.path.join(self.get_temp_folder(), fn))
|
self.image_files.append(image_path)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.debug(e)
|
log.exception(runlog)
|
||||||
log.debug(runlog)
|
|
||||||
return False
|
return False
|
||||||
self.num_pages = len(self.image_files)
|
self.num_pages = len(self.image_files)
|
||||||
# Create thumbnails
|
# Create thumbnails
|
||||||
|
|
|
@ -120,15 +120,16 @@ class PowerpointDocument(PresentationDocument):
|
||||||
Class which holds information and controls a single presentation.
|
Class which holds information and controls a single presentation.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, controller, presentation):
|
def __init__(self, controller, document_path):
|
||||||
"""
|
"""
|
||||||
Constructor, store information about the file and initialise.
|
Constructor, store information about the file and initialise.
|
||||||
|
|
||||||
:param controller:
|
:param controller:
|
||||||
:param presentation:
|
:param openlp.core.common.path.Path document_path: Path to the document to load
|
||||||
|
:rtype: None
|
||||||
"""
|
"""
|
||||||
log.debug('Init Presentation Powerpoint')
|
log.debug('Init Presentation Powerpoint')
|
||||||
super(PowerpointDocument, self).__init__(controller, presentation)
|
super().__init__(controller, document_path)
|
||||||
self.presentation = None
|
self.presentation = None
|
||||||
self.index_map = {}
|
self.index_map = {}
|
||||||
self.slide_count = 0
|
self.slide_count = 0
|
||||||
|
@ -145,7 +146,7 @@ class PowerpointDocument(PresentationDocument):
|
||||||
try:
|
try:
|
||||||
if not self.controller.process:
|
if not self.controller.process:
|
||||||
self.controller.start_process()
|
self.controller.start_process()
|
||||||
self.controller.process.Presentations.Open(os.path.normpath(self.file_path), False, False, False)
|
self.controller.process.Presentations.Open(str(self.file_path), False, False, False)
|
||||||
self.presentation = self.controller.process.Presentations(self.controller.process.Presentations.Count)
|
self.presentation = self.controller.process.Presentations(self.controller.process.Presentations.Count)
|
||||||
self.create_thumbnails()
|
self.create_thumbnails()
|
||||||
self.create_titles_and_notes()
|
self.create_titles_and_notes()
|
||||||
|
@ -177,7 +178,7 @@ class PowerpointDocument(PresentationDocument):
|
||||||
if not self.presentation.Slides(num + 1).SlideShowTransition.Hidden:
|
if not self.presentation.Slides(num + 1).SlideShowTransition.Hidden:
|
||||||
self.index_map[key] = num + 1
|
self.index_map[key] = num + 1
|
||||||
self.presentation.Slides(num + 1).Export(
|
self.presentation.Slides(num + 1).Export(
|
||||||
os.path.join(self.get_thumbnail_folder(), 'slide{key:d}.png'.format(key=key)), 'png', 320, 240)
|
str(self.get_thumbnail_folder() / 'slide{key:d}.png'.format(key=key)), 'png', 320, 240)
|
||||||
key += 1
|
key += 1
|
||||||
self.slide_count = key - 1
|
self.slide_count = key - 1
|
||||||
|
|
||||||
|
@ -363,9 +364,8 @@ class PowerpointDocument(PresentationDocument):
|
||||||
width=size.width(),
|
width=size.width(),
|
||||||
horizontal=(right - left)))
|
horizontal=(right - left)))
|
||||||
log.debug('window title: {title}'.format(title=window_title))
|
log.debug('window title: {title}'.format(title=window_title))
|
||||||
filename_root, filename_ext = os.path.splitext(os.path.basename(self.file_path))
|
|
||||||
if size.y() == top and size.height() == (bottom - top) and size.x() == left and \
|
if size.y() == top and size.height() == (bottom - top) and size.x() == left and \
|
||||||
size.width() == (right - left) and filename_root in window_title:
|
size.width() == (right - left) and self.file_path.stem in window_title:
|
||||||
log.debug('Found a match and will save the handle')
|
log.debug('Found a match and will save the handle')
|
||||||
self.presentation_hwnd = hwnd
|
self.presentation_hwnd = hwnd
|
||||||
# Stop powerpoint from flashing in the taskbar
|
# Stop powerpoint from flashing in the taskbar
|
||||||
|
|
|
@ -85,9 +85,9 @@ class PptviewController(PresentationController):
|
||||||
if self.process:
|
if self.process:
|
||||||
return
|
return
|
||||||
log.debug('start PPTView')
|
log.debug('start PPTView')
|
||||||
dll_path = os.path.join(str(AppLocation.get_directory(AppLocation.AppDir)),
|
dll_path = AppLocation.get_directory(AppLocation.AppDir) \
|
||||||
'plugins', 'presentations', 'lib', 'pptviewlib', 'pptviewlib.dll')
|
/ 'plugins' / 'presentations' / 'lib' / 'pptviewlib' / 'pptviewlib.dll'
|
||||||
self.process = cdll.LoadLibrary(dll_path)
|
self.process = cdll.LoadLibrary(str(dll_path))
|
||||||
if log.isEnabledFor(logging.DEBUG):
|
if log.isEnabledFor(logging.DEBUG):
|
||||||
self.process.SetDebug(1)
|
self.process.SetDebug(1)
|
||||||
|
|
||||||
|
@ -104,12 +104,15 @@ class PptviewDocument(PresentationDocument):
|
||||||
"""
|
"""
|
||||||
Class which holds information and controls a single presentation.
|
Class which holds information and controls a single presentation.
|
||||||
"""
|
"""
|
||||||
def __init__(self, controller, presentation):
|
def __init__(self, controller, document_path):
|
||||||
"""
|
"""
|
||||||
Constructor, store information about the file and initialise.
|
Constructor, store information about the file and initialise.
|
||||||
|
|
||||||
|
:param openlp.core.common.path.Path document_path: File path to the document to load
|
||||||
|
:rtype: None
|
||||||
"""
|
"""
|
||||||
log.debug('Init Presentation PowerPoint')
|
log.debug('Init Presentation PowerPoint')
|
||||||
super(PptviewDocument, self).__init__(controller, presentation)
|
super().__init__(controller, document_path)
|
||||||
self.presentation = None
|
self.presentation = None
|
||||||
self.ppt_id = None
|
self.ppt_id = None
|
||||||
self.blanked = False
|
self.blanked = False
|
||||||
|
@ -121,17 +124,16 @@ class PptviewDocument(PresentationDocument):
|
||||||
the background PptView task started earlier.
|
the background PptView task started earlier.
|
||||||
"""
|
"""
|
||||||
log.debug('LoadPresentation')
|
log.debug('LoadPresentation')
|
||||||
temp_folder = self.get_temp_folder()
|
temp_path = self.get_temp_folder()
|
||||||
size = ScreenList().current['size']
|
size = ScreenList().current['size']
|
||||||
rect = RECT(size.x(), size.y(), size.right(), size.bottom())
|
rect = RECT(size.x(), size.y(), size.right(), size.bottom())
|
||||||
self.file_path = os.path.normpath(self.file_path)
|
preview_path = temp_path / 'slide'
|
||||||
preview_path = os.path.join(temp_folder, 'slide')
|
|
||||||
# Ensure that the paths are null terminated
|
# Ensure that the paths are null terminated
|
||||||
byte_file_path = self.file_path.encode('utf-16-le') + b'\0'
|
file_path_utf16 = str(self.file_path).encode('utf-16-le') + b'\0'
|
||||||
preview_path = preview_path.encode('utf-16-le') + b'\0'
|
preview_path_utf16 = str(preview_path).encode('utf-16-le') + b'\0'
|
||||||
if not os.path.isdir(temp_folder):
|
if not temp_path.is_dir():
|
||||||
os.makedirs(temp_folder)
|
temp_path.mkdir(parents=True)
|
||||||
self.ppt_id = self.controller.process.OpenPPT(byte_file_path, None, rect, preview_path)
|
self.ppt_id = self.controller.process.OpenPPT(file_path_utf16, None, rect, preview_path_utf16)
|
||||||
if self.ppt_id >= 0:
|
if self.ppt_id >= 0:
|
||||||
self.create_thumbnails()
|
self.create_thumbnails()
|
||||||
self.stop_presentation()
|
self.stop_presentation()
|
||||||
|
@ -148,7 +150,7 @@ class PptviewDocument(PresentationDocument):
|
||||||
return
|
return
|
||||||
log.debug('create_thumbnails proceeding')
|
log.debug('create_thumbnails proceeding')
|
||||||
for idx in range(self.get_slide_count()):
|
for idx in range(self.get_slide_count()):
|
||||||
path = '{folder}\\slide{index}.bmp'.format(folder=self.get_temp_folder(), index=str(idx + 1))
|
path = self.get_temp_folder() / 'slide{index:d}.bmp'.format(index=idx + 1)
|
||||||
self.convert_thumbnail(path, idx + 1)
|
self.convert_thumbnail(path, idx + 1)
|
||||||
|
|
||||||
def create_titles_and_notes(self):
|
def create_titles_and_notes(self):
|
||||||
|
@ -161,13 +163,12 @@ class PptviewDocument(PresentationDocument):
|
||||||
"""
|
"""
|
||||||
titles = None
|
titles = None
|
||||||
notes = None
|
notes = None
|
||||||
filename = os.path.normpath(self.file_path)
|
|
||||||
# let's make sure we have a valid zipped presentation
|
# let's make sure we have a valid zipped presentation
|
||||||
if os.path.exists(filename) and zipfile.is_zipfile(filename):
|
if self.file_path.exists() and zipfile.is_zipfile(str(self.file_path)):
|
||||||
namespaces = {"p": "http://schemas.openxmlformats.org/presentationml/2006/main",
|
namespaces = {"p": "http://schemas.openxmlformats.org/presentationml/2006/main",
|
||||||
"a": "http://schemas.openxmlformats.org/drawingml/2006/main"}
|
"a": "http://schemas.openxmlformats.org/drawingml/2006/main"}
|
||||||
# open the file
|
# open the file
|
||||||
with zipfile.ZipFile(filename) as zip_file:
|
with zipfile.ZipFile(str(self.file_path)) as zip_file:
|
||||||
# find the presentation.xml to get the slide count
|
# find the presentation.xml to get the slide count
|
||||||
with zip_file.open('ppt/presentation.xml') as pres:
|
with zip_file.open('ppt/presentation.xml') as pres:
|
||||||
tree = ElementTree.parse(pres)
|
tree = ElementTree.parse(pres)
|
||||||
|
|
|
@ -19,15 +19,12 @@
|
||||||
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
|
||||||
import shutil
|
|
||||||
|
|
||||||
from PyQt5 import QtCore
|
from PyQt5 import QtCore
|
||||||
|
|
||||||
from openlp.core.common import Registry, AppLocation, Settings, check_directory_exists, md5_hash
|
from openlp.core.common import Registry, AppLocation, Settings, check_directory_exists, md5_hash
|
||||||
from openlp.core.common.path import Path
|
from openlp.core.common.path import Path, rmtree
|
||||||
from openlp.core.lib import create_thumb, validate_thumb
|
from openlp.core.lib import create_thumb, validate_thumb
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
@ -86,20 +83,27 @@ class PresentationDocument(object):
|
||||||
Returns a path to an image containing a preview for the requested slide
|
Returns a path to an image containing a preview for the requested slide
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def __init__(self, controller, name):
|
def __init__(self, controller, document_path):
|
||||||
"""
|
"""
|
||||||
Constructor for the PresentationController class
|
Constructor for the PresentationController class
|
||||||
|
|
||||||
|
:param controller:
|
||||||
|
:param openlp.core.common.path.Path document_path: Path to the document to load.
|
||||||
|
:rtype: None
|
||||||
"""
|
"""
|
||||||
self.controller = controller
|
self.controller = controller
|
||||||
self._setup(name)
|
self._setup(document_path)
|
||||||
|
|
||||||
def _setup(self, name):
|
def _setup(self, document_path):
|
||||||
"""
|
"""
|
||||||
Run some initial setup. This method is separate from __init__ in order to mock it out in tests.
|
Run some initial setup. This method is separate from __init__ in order to mock it out in tests.
|
||||||
|
|
||||||
|
:param openlp.core.common.path.Path document_path: Path to the document to load.
|
||||||
|
:rtype: None
|
||||||
"""
|
"""
|
||||||
self.slide_number = 0
|
self.slide_number = 0
|
||||||
self.file_path = name
|
self.file_path = document_path
|
||||||
check_directory_exists(Path(self.get_thumbnail_folder()))
|
check_directory_exists(self.get_thumbnail_folder())
|
||||||
|
|
||||||
def load_presentation(self):
|
def load_presentation(self):
|
||||||
"""
|
"""
|
||||||
|
@ -116,49 +120,54 @@ class PresentationDocument(object):
|
||||||
a file, e.g. thumbnails
|
a file, e.g. thumbnails
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
if os.path.exists(self.get_thumbnail_folder()):
|
thumbnail_folder_path = self.get_thumbnail_folder()
|
||||||
shutil.rmtree(self.get_thumbnail_folder())
|
temp_folder_path = self.get_temp_folder()
|
||||||
if os.path.exists(self.get_temp_folder()):
|
if thumbnail_folder_path.exists():
|
||||||
shutil.rmtree(self.get_temp_folder())
|
rmtree(thumbnail_folder_path)
|
||||||
|
if temp_folder_path.exists():
|
||||||
|
rmtree(temp_folder_path)
|
||||||
except OSError:
|
except OSError:
|
||||||
log.exception('Failed to delete presentation controller files')
|
log.exception('Failed to delete presentation controller files')
|
||||||
|
|
||||||
def get_file_name(self):
|
|
||||||
"""
|
|
||||||
Return just the filename of the presentation, without the directory
|
|
||||||
"""
|
|
||||||
return os.path.split(self.file_path)[1]
|
|
||||||
|
|
||||||
def get_thumbnail_folder(self):
|
def get_thumbnail_folder(self):
|
||||||
"""
|
"""
|
||||||
The location where thumbnail images will be stored
|
The location where thumbnail images will be stored
|
||||||
|
|
||||||
|
:return: The path to the thumbnail
|
||||||
|
:rtype: openlp.core.common.path.Path
|
||||||
"""
|
"""
|
||||||
# TODO: If statement can be removed when the upgrade path from 2.0.x to 2.2.x is no longer needed
|
# TODO: If statement can be removed when the upgrade path from 2.0.x to 2.2.x is no longer needed
|
||||||
if Settings().value('presentations/thumbnail_scheme') == 'md5':
|
if Settings().value('presentations/thumbnail_scheme') == 'md5':
|
||||||
folder = md5_hash(self.file_path.encode('utf-8'))
|
folder = md5_hash(bytes(self.file_path))
|
||||||
else:
|
else:
|
||||||
folder = self.get_file_name()
|
folder = self.file_path.name
|
||||||
return os.path.join(self.controller.thumbnail_folder, folder)
|
return Path(self.controller.thumbnail_folder, folder)
|
||||||
|
|
||||||
def get_temp_folder(self):
|
def get_temp_folder(self):
|
||||||
"""
|
"""
|
||||||
The location where thumbnail images will be stored
|
The location where thumbnail images will be stored
|
||||||
|
|
||||||
|
:return: The path to the temporary file folder
|
||||||
|
:rtype: openlp.core.common.path.Path
|
||||||
"""
|
"""
|
||||||
# TODO: If statement can be removed when the upgrade path from 2.0.x to 2.2.x is no longer needed
|
# TODO: If statement can be removed when the upgrade path from 2.0.x to 2.2.x is no longer needed
|
||||||
if Settings().value('presentations/thumbnail_scheme') == 'md5':
|
if Settings().value('presentations/thumbnail_scheme') == 'md5':
|
||||||
folder = md5_hash(self.file_path.encode('utf-8'))
|
folder = md5_hash(bytes(self.file_path))
|
||||||
else:
|
else:
|
||||||
folder = folder = self.get_file_name()
|
folder = self.file_path.name
|
||||||
return os.path.join(self.controller.temp_folder, folder)
|
return Path(self.controller.temp_folder, folder)
|
||||||
|
|
||||||
def check_thumbnails(self):
|
def check_thumbnails(self):
|
||||||
"""
|
"""
|
||||||
Returns ``True`` if the thumbnail images exist and are more recent than the powerpoint file.
|
Check that the last thumbnail image exists and is valid and are more recent than the powerpoint file.
|
||||||
|
|
||||||
|
:return: If the thumbnail is valid
|
||||||
|
:rtype: bool
|
||||||
"""
|
"""
|
||||||
last_image = self.get_thumbnail_path(self.get_slide_count(), True)
|
last_image_path = self.get_thumbnail_path(self.get_slide_count(), True)
|
||||||
if not (last_image and os.path.isfile(last_image)):
|
if not (last_image_path and last_image_path.is_file()):
|
||||||
return False
|
return False
|
||||||
return validate_thumb(self.file_path, last_image)
|
return validate_thumb(Path(self.file_path), Path(last_image_path))
|
||||||
|
|
||||||
def close_presentation(self):
|
def close_presentation(self):
|
||||||
"""
|
"""
|
||||||
|
@ -241,25 +250,31 @@ class PresentationDocument(object):
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def convert_thumbnail(self, file, idx):
|
def convert_thumbnail(self, image_path, index):
|
||||||
"""
|
"""
|
||||||
Convert the slide image the application made to a scaled 360px height .png image.
|
Convert the slide image the application made to a scaled 360px height .png image.
|
||||||
|
|
||||||
|
:param openlp.core.common.path.Path image_path: Path to the image to create a thumb nail of
|
||||||
|
:param int index: The index of the slide to create the thumbnail for.
|
||||||
|
:rtype: None
|
||||||
"""
|
"""
|
||||||
if self.check_thumbnails():
|
if self.check_thumbnails():
|
||||||
return
|
return
|
||||||
if os.path.isfile(file):
|
if image_path.is_file():
|
||||||
thumb_path = self.get_thumbnail_path(idx, False)
|
thumb_path = self.get_thumbnail_path(index, False)
|
||||||
create_thumb(file, thumb_path, False, QtCore.QSize(-1, 360))
|
create_thumb(str(image_path), str(thumb_path), False, QtCore.QSize(-1, 360))
|
||||||
|
|
||||||
def get_thumbnail_path(self, slide_no, check_exists):
|
def get_thumbnail_path(self, slide_no, check_exists=False):
|
||||||
"""
|
"""
|
||||||
Returns an image path containing a preview for the requested slide
|
Returns an image path containing a preview for the requested slide
|
||||||
|
|
||||||
:param slide_no: The slide an image is required for, starting at 1
|
:param int slide_no: The slide an image is required for, starting at 1
|
||||||
:param check_exists:
|
:param bool check_exists: Check if the generated path exists
|
||||||
|
:return: The path, or None if the :param:`check_exists` is True and the file does not exist
|
||||||
|
:rtype: openlp.core.common.path.Path | None
|
||||||
"""
|
"""
|
||||||
path = os.path.join(self.get_thumbnail_folder(), self.controller.thumbnail_prefix + str(slide_no) + '.png')
|
path = self.get_thumbnail_folder() / (self.controller.thumbnail_prefix + str(slide_no) + '.png')
|
||||||
if os.path.isfile(path) or not check_exists:
|
if path.is_file() or not check_exists:
|
||||||
return path
|
return path
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
@ -302,44 +317,38 @@ class PresentationDocument(object):
|
||||||
Reads the titles from the titles file and
|
Reads the titles from the titles file and
|
||||||
the notes files and returns the content in two lists
|
the notes files and returns the content in two lists
|
||||||
"""
|
"""
|
||||||
titles = []
|
|
||||||
notes = []
|
notes = []
|
||||||
titles_file = os.path.join(self.get_thumbnail_folder(), 'titles.txt')
|
titles_path = self.get_thumbnail_folder() / 'titles.txt'
|
||||||
if os.path.exists(titles_file):
|
try:
|
||||||
try:
|
titles = titles_path.read_text().splitlines()
|
||||||
with open(titles_file, encoding='utf-8') as fi:
|
except:
|
||||||
titles = fi.read().splitlines()
|
log.exception('Failed to open/read existing titles file')
|
||||||
except:
|
titles = []
|
||||||
log.exception('Failed to open/read existing titles file')
|
|
||||||
titles = []
|
|
||||||
for slide_no, title in enumerate(titles, 1):
|
for slide_no, title in enumerate(titles, 1):
|
||||||
notes_file = os.path.join(self.get_thumbnail_folder(), 'slideNotes{number:d}.txt'.format(number=slide_no))
|
notes_path = self.get_thumbnail_folder() / 'slideNotes{number:d}.txt'.format(number=slide_no)
|
||||||
note = ''
|
try:
|
||||||
if os.path.exists(notes_file):
|
note = notes_path.read_text()
|
||||||
try:
|
except:
|
||||||
with open(notes_file, encoding='utf-8') as fn:
|
log.exception('Failed to open/read notes file')
|
||||||
note = fn.read()
|
note = ''
|
||||||
except:
|
|
||||||
log.exception('Failed to open/read notes file')
|
|
||||||
note = ''
|
|
||||||
notes.append(note)
|
notes.append(note)
|
||||||
return titles, notes
|
return titles, notes
|
||||||
|
|
||||||
def save_titles_and_notes(self, titles, notes):
|
def save_titles_and_notes(self, titles, notes):
|
||||||
"""
|
"""
|
||||||
Performs the actual persisting of titles to the titles.txt
|
Performs the actual persisting of titles to the titles.txt and notes to the slideNote%.txt
|
||||||
and notes to the slideNote%.txt
|
|
||||||
|
:param list[str] titles: The titles to save
|
||||||
|
:param list[str] notes: The notes to save
|
||||||
|
:rtype: None
|
||||||
"""
|
"""
|
||||||
if titles:
|
if titles:
|
||||||
titles_file = os.path.join(self.get_thumbnail_folder(), 'titles.txt')
|
titles_path = self.get_thumbnail_folder() / 'titles.txt'
|
||||||
with open(titles_file, mode='wt', encoding='utf-8') as fo:
|
titles_path.write_text('\n'.join(titles))
|
||||||
fo.writelines(titles)
|
|
||||||
if notes:
|
if notes:
|
||||||
for slide_no, note in enumerate(notes, 1):
|
for slide_no, note in enumerate(notes, 1):
|
||||||
notes_file = os.path.join(self.get_thumbnail_folder(),
|
notes_path = self.get_thumbnail_folder() / 'slideNotes{number:d}.txt'.format(number=slide_no)
|
||||||
'slideNotes{number:d}.txt'.format(number=slide_no))
|
notes_path.write_text(note)
|
||||||
with open(notes_file, mode='wt', encoding='utf-8') as fn:
|
|
||||||
fn.write(note)
|
|
||||||
|
|
||||||
|
|
||||||
class PresentationController(object):
|
class PresentationController(object):
|
||||||
|
@ -416,12 +425,11 @@ class PresentationController(object):
|
||||||
self.document_class = document_class
|
self.document_class = document_class
|
||||||
self.settings_section = self.plugin.settings_section
|
self.settings_section = self.plugin.settings_section
|
||||||
self.available = None
|
self.available = None
|
||||||
self.temp_folder = os.path.join(str(AppLocation.get_section_data_path(self.settings_section)), name)
|
self.temp_folder = AppLocation.get_section_data_path(self.settings_section) / name
|
||||||
self.thumbnail_folder = os.path.join(
|
self.thumbnail_folder = AppLocation.get_section_data_path(self.settings_section) / 'thumbnails'
|
||||||
str(AppLocation.get_section_data_path(self.settings_section)), 'thumbnails')
|
|
||||||
self.thumbnail_prefix = 'slide'
|
self.thumbnail_prefix = 'slide'
|
||||||
check_directory_exists(Path(self.thumbnail_folder))
|
check_directory_exists(self.thumbnail_folder)
|
||||||
check_directory_exists(Path(self.temp_folder))
|
check_directory_exists(self.temp_folder)
|
||||||
|
|
||||||
def enabled(self):
|
def enabled(self):
|
||||||
"""
|
"""
|
||||||
|
@ -456,11 +464,15 @@ class PresentationController(object):
|
||||||
log.debug('Kill')
|
log.debug('Kill')
|
||||||
self.close_presentation()
|
self.close_presentation()
|
||||||
|
|
||||||
def add_document(self, name):
|
def add_document(self, document_path):
|
||||||
"""
|
"""
|
||||||
Called when a new presentation document is opened.
|
Called when a new presentation document is opened.
|
||||||
|
|
||||||
|
:param openlp.core.common.path.Path document_path: Path to the document to load
|
||||||
|
:return: The document
|
||||||
|
:rtype: PresentationDocument
|
||||||
"""
|
"""
|
||||||
document = self.document_class(self, name)
|
document = self.document_class(self, document_path)
|
||||||
self.docs.append(document)
|
self.docs.append(document)
|
||||||
return document
|
return document
|
||||||
|
|
||||||
|
|
|
@ -38,7 +38,6 @@ class PresentationTab(SettingsTab):
|
||||||
"""
|
"""
|
||||||
Constructor
|
Constructor
|
||||||
"""
|
"""
|
||||||
self.parent = parent
|
|
||||||
self.controllers = controllers
|
self.controllers = controllers
|
||||||
super(PresentationTab, self).__init__(parent, title, visible_title, icon_path)
|
super(PresentationTab, self).__init__(parent, title, visible_title, icon_path)
|
||||||
self.activated = False
|
self.activated = False
|
||||||
|
@ -194,7 +193,7 @@ class PresentationTab(SettingsTab):
|
||||||
pdf_program_path = self.program_path_edit.path
|
pdf_program_path = self.program_path_edit.path
|
||||||
enable_pdf_program = self.pdf_program_check_box.checkState()
|
enable_pdf_program = self.pdf_program_check_box.checkState()
|
||||||
# If the given program is blank disable using the program
|
# If the given program is blank disable using the program
|
||||||
if not pdf_program_path:
|
if pdf_program_path is None:
|
||||||
enable_pdf_program = 0
|
enable_pdf_program = 0
|
||||||
if pdf_program_path != Settings().value(self.settings_section + '/pdf_program'):
|
if pdf_program_path != Settings().value(self.settings_section + '/pdf_program'):
|
||||||
Settings().setValue(self.settings_section + '/pdf_program', pdf_program_path)
|
Settings().setValue(self.settings_section + '/pdf_program', pdf_program_path)
|
||||||
|
@ -220,9 +219,11 @@ class PresentationTab(SettingsTab):
|
||||||
|
|
||||||
def on_program_path_edit_path_changed(self, new_path):
|
def on_program_path_edit_path_changed(self, new_path):
|
||||||
"""
|
"""
|
||||||
Select the mudraw or ghostscript binary that should be used.
|
Handle the `pathEditChanged` signal from program_path_edit
|
||||||
|
|
||||||
|
:param openlp.core.common.path.Path new_path: File path to the new program
|
||||||
|
:rtype: None
|
||||||
"""
|
"""
|
||||||
new_path = path_to_str(new_path)
|
|
||||||
if new_path:
|
if new_path:
|
||||||
if not PdfController.process_check_binary(new_path):
|
if not PdfController.process_check_binary(new_path):
|
||||||
critical_error_message_box(UiStrings().Error,
|
critical_error_message_box(UiStrings().Error,
|
||||||
|
|
|
@ -19,10 +19,11 @@
|
||||||
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
"""
|
||||||
|
Download and "install" the remote web client
|
||||||
|
"""
|
||||||
import os
|
import os
|
||||||
import zipfile
|
from zipfile import ZipFile
|
||||||
import urllib.error
|
|
||||||
|
|
||||||
from openlp.core.common import AppLocation, Registry
|
from openlp.core.common import AppLocation, Registry
|
||||||
from openlp.core.common.httputils import url_get_file, get_web_page, get_url_file_size
|
from openlp.core.common.httputils import url_get_file, get_web_page, get_url_file_size
|
||||||
|
@ -38,7 +39,7 @@ def deploy_zipfile(app_root, zip_name):
|
||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
zip_file = os.path.join(app_root, zip_name)
|
zip_file = os.path.join(app_root, zip_name)
|
||||||
web_zip = zipfile.ZipFile(zip_file)
|
web_zip = ZipFile(zip_file)
|
||||||
web_zip.extractall(app_root)
|
web_zip.extractall(app_root)
|
||||||
|
|
||||||
|
|
||||||
|
@ -48,11 +49,10 @@ def download_sha256():
|
||||||
"""
|
"""
|
||||||
user_agent = 'OpenLP/' + Registry().get('application').applicationVersion()
|
user_agent = 'OpenLP/' + Registry().get('application').applicationVersion()
|
||||||
try:
|
try:
|
||||||
web_config = get_web_page('{host}{name}'.format(host='https://get.openlp.org/webclient/', name='download.cfg'),
|
web_config = get_web_page('https://get.openlp.org/webclient/download.cfg', headers={'User-Agent': user_agent})
|
||||||
header=('User-Agent', user_agent))
|
except ConnectionError:
|
||||||
except (urllib.error.URLError, ConnectionError) as err:
|
|
||||||
return False
|
return False
|
||||||
file_bits = web_config.read().decode('utf-8').split()
|
file_bits = web_config.split()
|
||||||
return file_bits[0], file_bits[2]
|
return file_bits[0], file_bits[2]
|
||||||
|
|
||||||
|
|
||||||
|
@ -64,6 +64,6 @@ def download_and_check(callback=None):
|
||||||
file_size = get_url_file_size('https://get.openlp.org/webclient/site.zip')
|
file_size = get_url_file_size('https://get.openlp.org/webclient/site.zip')
|
||||||
callback.setRange(0, file_size)
|
callback.setRange(0, file_size)
|
||||||
if url_get_file(callback, '{host}{name}'.format(host='https://get.openlp.org/webclient/', name='site.zip'),
|
if url_get_file(callback, '{host}{name}'.format(host='https://get.openlp.org/webclient/', name='site.zip'),
|
||||||
os.path.join(str(AppLocation.get_section_data_path('remotes')), 'site.zip'),
|
AppLocation.get_section_data_path('remotes') / 'site.zip',
|
||||||
sha256=sha256):
|
sha256=sha256):
|
||||||
deploy_zipfile(str(AppLocation.get_section_data_path('remotes')), 'site.zip')
|
deploy_zipfile(str(AppLocation.get_section_data_path('remotes')), 'site.zip')
|
||||||
|
|
|
@ -62,7 +62,7 @@ import re
|
||||||
from lxml import etree, objectify
|
from lxml import etree, objectify
|
||||||
|
|
||||||
from openlp.core.common import translate, Settings
|
from openlp.core.common import translate, Settings
|
||||||
from openlp.core.common.versionchecker import get_application_version
|
from openlp.core.version import get_version
|
||||||
from openlp.core.lib import FormattingTags
|
from openlp.core.lib import FormattingTags
|
||||||
from openlp.plugins.songs.lib import VerseType, clean_song
|
from openlp.plugins.songs.lib import VerseType, clean_song
|
||||||
from openlp.plugins.songs.lib.db import Author, AuthorType, Book, Song, Topic
|
from openlp.plugins.songs.lib.db import Author, AuthorType, Book, Song, Topic
|
||||||
|
@ -234,7 +234,7 @@ class OpenLyrics(object):
|
||||||
# Append the necessary meta data to the song.
|
# Append the necessary meta data to the song.
|
||||||
song_xml.set('xmlns', NAMESPACE)
|
song_xml.set('xmlns', NAMESPACE)
|
||||||
song_xml.set('version', OpenLyrics.IMPLEMENTED_VERSION)
|
song_xml.set('version', OpenLyrics.IMPLEMENTED_VERSION)
|
||||||
application_name = 'OpenLP ' + get_application_version()['version']
|
application_name = 'OpenLP ' + get_version()['version']
|
||||||
song_xml.set('createdIn', application_name)
|
song_xml.set('createdIn', application_name)
|
||||||
song_xml.set('modifiedIn', application_name)
|
song_xml.set('modifiedIn', application_name)
|
||||||
# "Convert" 2012-08-27 11:49:15 to 2012-08-27T11:49:15.
|
# "Convert" 2012-08-27 11:49:15 to 2012-08-27T11:49:15.
|
||||||
|
|
|
@ -25,10 +25,10 @@ The :mod:`db` module provides the ability to provide a csv file of all songs
|
||||||
import csv
|
import csv
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from PyQt5 import QtWidgets
|
|
||||||
|
|
||||||
from openlp.core.common import Registry, translate
|
from openlp.core.common import Registry, translate
|
||||||
|
from openlp.core.common.path import Path
|
||||||
from openlp.core.lib.ui import critical_error_message_box
|
from openlp.core.lib.ui import critical_error_message_box
|
||||||
|
from openlp.core.ui.lib.filedialog import FileDialog
|
||||||
from openlp.plugins.songs.lib.db import Song
|
from openlp.plugins.songs.lib.db import Song
|
||||||
|
|
||||||
|
|
||||||
|
@ -42,58 +42,55 @@ def report_song_list():
|
||||||
"""
|
"""
|
||||||
main_window = Registry().get('main_window')
|
main_window = Registry().get('main_window')
|
||||||
plugin = Registry().get('songs').plugin
|
plugin = Registry().get('songs').plugin
|
||||||
report_file_name, filter_used = QtWidgets.QFileDialog.getSaveFileName(
|
report_file_path, filter_used = FileDialog.getSaveFileName(
|
||||||
main_window,
|
main_window,
|
||||||
translate('SongPlugin.ReportSongList', 'Save File'),
|
translate('SongPlugin.ReportSongList', 'Save File'),
|
||||||
translate('SongPlugin.ReportSongList', 'song_extract.csv'),
|
Path(translate('SongPlugin.ReportSongList', 'song_extract.csv')),
|
||||||
translate('SongPlugin.ReportSongList', 'CSV format (*.csv)'))
|
translate('SongPlugin.ReportSongList', 'CSV format (*.csv)'))
|
||||||
|
|
||||||
if not report_file_name:
|
if report_file_path is None:
|
||||||
main_window.error_message(
|
main_window.error_message(
|
||||||
translate('SongPlugin.ReportSongList', 'Output Path Not Selected'),
|
translate('SongPlugin.ReportSongList', 'Output Path Not Selected'),
|
||||||
translate('SongPlugin.ReportSongList', 'You have not set a valid output location for your '
|
translate('SongPlugin.ReportSongList', 'You have not set a valid output location for your report. \n'
|
||||||
'report. \nPlease select an existing path '
|
'Please select an existing path on your computer.')
|
||||||
'on your computer.')
|
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
if not report_file_name.endswith('csv'):
|
report_file_path.with_suffix('.csv')
|
||||||
report_file_name += '.csv'
|
|
||||||
file_handle = None
|
|
||||||
Registry().get('application').set_busy_cursor()
|
Registry().get('application').set_busy_cursor()
|
||||||
try:
|
try:
|
||||||
file_handle = open(report_file_name, 'wt')
|
with report_file_path.open('wt') as file_handle:
|
||||||
fieldnames = ('Title', 'Alternative Title', 'Copyright', 'Author(s)', 'Song Book', 'Topic')
|
fieldnames = ('Title', 'Alternative Title', 'Copyright', 'Author(s)', 'Song Book', 'Topic')
|
||||||
writer = csv.DictWriter(file_handle, fieldnames=fieldnames, quoting=csv.QUOTE_ALL)
|
writer = csv.DictWriter(file_handle, fieldnames=fieldnames, quoting=csv.QUOTE_ALL)
|
||||||
headers = dict((n, n) for n in fieldnames)
|
headers = dict((n, n) for n in fieldnames)
|
||||||
writer.writerow(headers)
|
writer.writerow(headers)
|
||||||
song_list = plugin.manager.get_all_objects(Song)
|
song_list = plugin.manager.get_all_objects(Song)
|
||||||
for song in song_list:
|
for song in song_list:
|
||||||
author_list = []
|
author_list = []
|
||||||
for author_song in song.authors_songs:
|
for author_song in song.authors_songs:
|
||||||
author_list.append(author_song.author.display_name)
|
author_list.append(author_song.author.display_name)
|
||||||
author_string = ' | '.join(author_list)
|
author_string = ' | '.join(author_list)
|
||||||
book_list = []
|
book_list = []
|
||||||
for book_song in song.songbook_entries:
|
for book_song in song.songbook_entries:
|
||||||
if hasattr(book_song, 'entry') and book_song.entry:
|
if hasattr(book_song, 'entry') and book_song.entry:
|
||||||
book_list.append('{name} #{entry}'.format(name=book_song.songbook.name, entry=book_song.entry))
|
book_list.append('{name} #{entry}'.format(name=book_song.songbook.name, entry=book_song.entry))
|
||||||
book_string = ' | '.join(book_list)
|
book_string = ' | '.join(book_list)
|
||||||
topic_list = []
|
topic_list = []
|
||||||
for topic_song in song.topics:
|
for topic_song in song.topics:
|
||||||
if hasattr(topic_song, 'name'):
|
if hasattr(topic_song, 'name'):
|
||||||
topic_list.append(topic_song.name)
|
topic_list.append(topic_song.name)
|
||||||
topic_string = ' | '.join(topic_list)
|
topic_string = ' | '.join(topic_list)
|
||||||
writer.writerow({'Title': song.title,
|
writer.writerow({'Title': song.title,
|
||||||
'Alternative Title': song.alternate_title,
|
'Alternative Title': song.alternate_title,
|
||||||
'Copyright': song.copyright,
|
'Copyright': song.copyright,
|
||||||
'Author(s)': author_string,
|
'Author(s)': author_string,
|
||||||
'Song Book': book_string,
|
'Song Book': book_string,
|
||||||
'Topic': topic_string})
|
'Topic': topic_string})
|
||||||
Registry().get('application').set_normal_cursor()
|
Registry().get('application').set_normal_cursor()
|
||||||
main_window.information_message(
|
main_window.information_message(
|
||||||
translate('SongPlugin.ReportSongList', 'Report Creation'),
|
translate('SongPlugin.ReportSongList', 'Report Creation'),
|
||||||
translate('SongPlugin.ReportSongList',
|
translate('SongPlugin.ReportSongList',
|
||||||
'Report \n{name} \nhas been successfully created. ').format(name=report_file_name)
|
'Report \n{name} \nhas been successfully created. ').format(name=report_file_path)
|
||||||
)
|
)
|
||||||
except OSError as ose:
|
except OSError as ose:
|
||||||
Registry().get('application').set_normal_cursor()
|
Registry().get('application').set_normal_cursor()
|
||||||
log.exception('Failed to write out song usage records')
|
log.exception('Failed to write out song usage records')
|
||||||
|
@ -101,6 +98,3 @@ def report_song_list():
|
||||||
translate('SongPlugin.ReportSongList',
|
translate('SongPlugin.ReportSongList',
|
||||||
'An error occurred while extracting: {error}'
|
'An error occurred while extracting: {error}'
|
||||||
).format(error=ose.strerror))
|
).format(error=ose.strerror))
|
||||||
finally:
|
|
||||||
if file_handle:
|
|
||||||
file_handle.close()
|
|
||||||
|
|
|
@ -19,7 +19,6 @@
|
||||||
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
from PyQt5 import QtCore, QtWidgets
|
from PyQt5 import QtCore, QtWidgets
|
||||||
|
|
||||||
from openlp.core.common import translate
|
from openlp.core.common import translate
|
||||||
|
|
|
@ -19,7 +19,6 @@
|
||||||
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
@ -60,7 +59,7 @@ class SongUsageDetailForm(QtWidgets.QDialog, Ui_SongUsageDetailDialog, RegistryP
|
||||||
|
|
||||||
def on_report_path_edit_path_changed(self, file_path):
|
def on_report_path_edit_path_changed(self, file_path):
|
||||||
"""
|
"""
|
||||||
Called when the path in the `PathEdit` has changed
|
Handle the `pathEditChanged` signal from report_path_edit
|
||||||
|
|
||||||
:param openlp.core.common.path.Path file_path: The new path.
|
:param openlp.core.common.path.Path file_path: The new path.
|
||||||
:rtype: None
|
:rtype: None
|
||||||
|
@ -72,7 +71,7 @@ class SongUsageDetailForm(QtWidgets.QDialog, Ui_SongUsageDetailDialog, RegistryP
|
||||||
Ok was triggered so lets save the data and run the report
|
Ok was triggered so lets save the data and run the report
|
||||||
"""
|
"""
|
||||||
log.debug('accept')
|
log.debug('accept')
|
||||||
path = path_to_str(self.report_path_edit.path)
|
path = self.report_path_edit.path
|
||||||
if not path:
|
if not path:
|
||||||
self.main_window.error_message(
|
self.main_window.error_message(
|
||||||
translate('SongUsagePlugin.SongUsageDetailForm', 'Output Path Not Selected'),
|
translate('SongUsagePlugin.SongUsageDetailForm', 'Output Path Not Selected'),
|
||||||
|
@ -80,7 +79,7 @@ class SongUsageDetailForm(QtWidgets.QDialog, Ui_SongUsageDetailDialog, RegistryP
|
||||||
' song usage report. \nPlease select an existing path on your computer.')
|
' song usage report. \nPlease select an existing path on your computer.')
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
check_directory_exists(Path(path))
|
check_directory_exists(path)
|
||||||
file_name = translate('SongUsagePlugin.SongUsageDetailForm',
|
file_name = translate('SongUsagePlugin.SongUsageDetailForm',
|
||||||
'usage_detail_{old}_{new}.txt'
|
'usage_detail_{old}_{new}.txt'
|
||||||
).format(old=self.from_date_calendar.selectedDate().toString('ddMMyyyy'),
|
).format(old=self.from_date_calendar.selectedDate().toString('ddMMyyyy'),
|
||||||
|
@ -91,29 +90,25 @@ class SongUsageDetailForm(QtWidgets.QDialog, Ui_SongUsageDetailDialog, RegistryP
|
||||||
SongUsageItem, and_(SongUsageItem.usagedate >= self.from_date_calendar.selectedDate().toPyDate(),
|
SongUsageItem, and_(SongUsageItem.usagedate >= self.from_date_calendar.selectedDate().toPyDate(),
|
||||||
SongUsageItem.usagedate < self.to_date_calendar.selectedDate().toPyDate()),
|
SongUsageItem.usagedate < self.to_date_calendar.selectedDate().toPyDate()),
|
||||||
[SongUsageItem.usagedate, SongUsageItem.usagetime])
|
[SongUsageItem.usagedate, SongUsageItem.usagetime])
|
||||||
report_file_name = os.path.join(path, file_name)
|
report_file_name = path / file_name
|
||||||
file_handle = None
|
|
||||||
try:
|
try:
|
||||||
file_handle = open(report_file_name, 'wb')
|
with report_file_name.open('wb') as file_handle:
|
||||||
for instance in usage:
|
for instance in usage:
|
||||||
record = ('\"{date}\",\"{time}\",\"{title}\",\"{copyright}\",\"{ccli}\",\"{authors}\",'
|
record = ('\"{date}\",\"{time}\",\"{title}\",\"{copyright}\",\"{ccli}\",\"{authors}\",'
|
||||||
'\"{name}\",\"{source}\"\n').format(date=instance.usagedate, time=instance.usagetime,
|
'\"{name}\",\"{source}\"\n').format(date=instance.usagedate, time=instance.usagetime,
|
||||||
title=instance.title, copyright=instance.copyright,
|
title=instance.title, copyright=instance.copyright,
|
||||||
ccli=instance.ccl_number, authors=instance.authors,
|
ccli=instance.ccl_number, authors=instance.authors,
|
||||||
name=instance.plugin_name, source=instance.source)
|
name=instance.plugin_name, source=instance.source)
|
||||||
file_handle.write(record.encode('utf-8'))
|
file_handle.write(record.encode('utf-8'))
|
||||||
self.main_window.information_message(
|
self.main_window.information_message(
|
||||||
translate('SongUsagePlugin.SongUsageDetailForm', 'Report Creation'),
|
translate('SongUsagePlugin.SongUsageDetailForm', 'Report Creation'),
|
||||||
translate('SongUsagePlugin.SongUsageDetailForm',
|
translate('SongUsagePlugin.SongUsageDetailForm',
|
||||||
'Report \n{name} \nhas been successfully created. ').format(name=report_file_name)
|
'Report \n{name} \nhas been successfully created. ').format(name=report_file_name)
|
||||||
)
|
)
|
||||||
except OSError as ose:
|
except OSError as ose:
|
||||||
log.exception('Failed to write out song usage records')
|
log.exception('Failed to write out song usage records')
|
||||||
critical_error_message_box(translate('SongUsagePlugin.SongUsageDetailForm', 'Report Creation Failed'),
|
critical_error_message_box(translate('SongUsagePlugin.SongUsageDetailForm', 'Report Creation Failed'),
|
||||||
translate('SongUsagePlugin.SongUsageDetailForm',
|
translate('SongUsagePlugin.SongUsageDetailForm',
|
||||||
'An error occurred while creating the report: {error}'
|
'An error occurred while creating the report: {error}'
|
||||||
).format(error=ose.strerror))
|
).format(error=ose.strerror))
|
||||||
finally:
|
|
||||||
if file_handle:
|
|
||||||
file_handle.close()
|
|
||||||
self.close()
|
self.close()
|
||||||
|
|
|
@ -12,7 +12,7 @@ environment:
|
||||||
|
|
||||||
install:
|
install:
|
||||||
# Install dependencies from pypi
|
# Install dependencies from pypi
|
||||||
- "%PYTHON%\\python.exe -m pip install sqlalchemy alembic chardet beautifulsoup4 Mako nose mock pyodbc==4.0.8 psycopg2 pypiwin32 pyenchant websockets asyncio waitress six webob"
|
- "%PYTHON%\\python.exe -m pip install sqlalchemy alembic chardet beautifulsoup4 Mako nose mock pyodbc==4.0.8 psycopg2 pypiwin32 pyenchant websockets asyncio waitress six webob requests"
|
||||||
# Install mysql dependency
|
# Install mysql dependency
|
||||||
- "%PYTHON%\\python.exe -m pip install http://cdn.mysql.com/Downloads/Connector-Python/mysql-connector-python-2.0.4.zip#md5=3df394d89300db95163f17c843ef49df"
|
- "%PYTHON%\\python.exe -m pip install http://cdn.mysql.com/Downloads/Connector-Python/mysql-connector-python-2.0.4.zip#md5=3df394d89300db95163f17c843ef49df"
|
||||||
# Download and install lxml and pyicu (originally from http://www.lfd.uci.edu/~gohlke/pythonlibs/)
|
# Download and install lxml and pyicu (originally from http://www.lfd.uci.edu/~gohlke/pythonlibs/)
|
||||||
|
|
|
@ -26,7 +26,7 @@ This script is used to check dependencies of OpenLP. It checks availability
|
||||||
of required python modules and their version. To verify availability of Python
|
of required python modules and their version. To verify availability of Python
|
||||||
modules, simply run this script::
|
modules, simply run this script::
|
||||||
|
|
||||||
@:~$ ./check_dependencies.py
|
$ ./check_dependencies.py
|
||||||
|
|
||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
|
@ -45,7 +45,7 @@ IS_MAC = sys.platform.startswith('dar')
|
||||||
|
|
||||||
|
|
||||||
VERS = {
|
VERS = {
|
||||||
'Python': '3.0',
|
'Python': '3.4',
|
||||||
'PyQt5': '5.0',
|
'PyQt5': '5.0',
|
||||||
'Qt5': '5.0',
|
'Qt5': '5.0',
|
||||||
'sqlalchemy': '0.5',
|
'sqlalchemy': '0.5',
|
||||||
|
@ -97,7 +97,8 @@ MODULES = [
|
||||||
'asyncio',
|
'asyncio',
|
||||||
'waitress',
|
'waitress',
|
||||||
'six',
|
'six',
|
||||||
'webob'
|
'webob',
|
||||||
|
'requests'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,204 @@
|
||||||
|
# -*- 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.version package.
|
||||||
|
"""
|
||||||
|
import sys
|
||||||
|
from datetime import date
|
||||||
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
|
from requests.exceptions import ConnectionError
|
||||||
|
|
||||||
|
from openlp.core.version import VersionWorker, check_for_update, get_version, update_check_date
|
||||||
|
|
||||||
|
|
||||||
|
def test_worker_init():
|
||||||
|
"""Test the VersionWorker constructor"""
|
||||||
|
# GIVEN: A last check date and a current version
|
||||||
|
last_check_date = '1970-01-01'
|
||||||
|
current_version = '2.0'
|
||||||
|
|
||||||
|
# WHEN: A worker is created
|
||||||
|
worker = VersionWorker(last_check_date, current_version)
|
||||||
|
|
||||||
|
# THEN: The correct attributes should have been set
|
||||||
|
assert worker.last_check_date == last_check_date
|
||||||
|
assert worker.current_version == current_version
|
||||||
|
|
||||||
|
|
||||||
|
@patch('openlp.core.version.platform')
|
||||||
|
@patch('openlp.core.version.requests')
|
||||||
|
def test_worker_start(mock_requests, mock_platform):
|
||||||
|
"""Test the VersionWorkder.start() method"""
|
||||||
|
# GIVEN: A last check date, current version, and an instance of worker
|
||||||
|
last_check_date = '1970-01-01'
|
||||||
|
current_version = {'full': '2.0', 'version': '2.0', 'build': None}
|
||||||
|
mock_platform.system.return_value = 'Linux'
|
||||||
|
mock_platform.release.return_value = '4.12.0-1-amd64'
|
||||||
|
mock_requests.get.return_value = MagicMock(text='2.4.6')
|
||||||
|
worker = VersionWorker(last_check_date, current_version)
|
||||||
|
|
||||||
|
# WHEN: The worker is run
|
||||||
|
with patch.object(worker, 'new_version') as mock_new_version, \
|
||||||
|
patch.object(worker, 'quit') as mock_quit:
|
||||||
|
worker.start()
|
||||||
|
|
||||||
|
# THEN: The check completes and the signal is emitted
|
||||||
|
expected_download_url = 'http://www.openlp.org/files/version.txt'
|
||||||
|
expected_headers = {'User-Agent': 'OpenLP/2.0 Linux/4.12.0-1-amd64; '}
|
||||||
|
mock_requests.get.assert_called_once_with(expected_download_url, headers=expected_headers)
|
||||||
|
mock_new_version.emit.assert_called_once_with('2.4.6')
|
||||||
|
mock_quit.emit.assert_called_once_with()
|
||||||
|
|
||||||
|
|
||||||
|
@patch('openlp.core.version.platform')
|
||||||
|
@patch('openlp.core.version.requests')
|
||||||
|
def test_worker_start_dev_version(mock_requests, mock_platform):
|
||||||
|
"""Test the VersionWorkder.start() method for dev versions"""
|
||||||
|
# GIVEN: A last check date, current version, and an instance of worker
|
||||||
|
last_check_date = '1970-01-01'
|
||||||
|
current_version = {'full': '2.1.3', 'version': '2.1.3', 'build': None}
|
||||||
|
mock_platform.system.return_value = 'Linux'
|
||||||
|
mock_platform.release.return_value = '4.12.0-1-amd64'
|
||||||
|
mock_requests.get.return_value = MagicMock(text='2.4.6')
|
||||||
|
worker = VersionWorker(last_check_date, current_version)
|
||||||
|
|
||||||
|
# WHEN: The worker is run
|
||||||
|
with patch.object(worker, 'new_version') as mock_new_version, \
|
||||||
|
patch.object(worker, 'quit') as mock_quit:
|
||||||
|
worker.start()
|
||||||
|
|
||||||
|
# THEN: The check completes and the signal is emitted
|
||||||
|
expected_download_url = 'http://www.openlp.org/files/dev_version.txt'
|
||||||
|
expected_headers = {'User-Agent': 'OpenLP/2.1.3 Linux/4.12.0-1-amd64; '}
|
||||||
|
mock_requests.get.assert_called_once_with(expected_download_url, headers=expected_headers)
|
||||||
|
mock_new_version.emit.assert_called_once_with('2.4.6')
|
||||||
|
mock_quit.emit.assert_called_once_with()
|
||||||
|
|
||||||
|
|
||||||
|
@patch('openlp.core.version.platform')
|
||||||
|
@patch('openlp.core.version.requests')
|
||||||
|
def test_worker_start_nightly_version(mock_requests, mock_platform):
|
||||||
|
"""Test the VersionWorkder.start() method for nightlies"""
|
||||||
|
# GIVEN: A last check date, current version, and an instance of worker
|
||||||
|
last_check_date = '1970-01-01'
|
||||||
|
current_version = {'full': '2.1-bzr2345', 'version': '2.1', 'build': '2345'}
|
||||||
|
mock_platform.system.return_value = 'Linux'
|
||||||
|
mock_platform.release.return_value = '4.12.0-1-amd64'
|
||||||
|
mock_requests.get.return_value = MagicMock(text='2.4.6')
|
||||||
|
worker = VersionWorker(last_check_date, current_version)
|
||||||
|
|
||||||
|
# WHEN: The worker is run
|
||||||
|
with patch.object(worker, 'new_version') as mock_new_version, \
|
||||||
|
patch.object(worker, 'quit') as mock_quit:
|
||||||
|
worker.start()
|
||||||
|
|
||||||
|
# THEN: The check completes and the signal is emitted
|
||||||
|
expected_download_url = 'http://www.openlp.org/files/nightly_version.txt'
|
||||||
|
expected_headers = {'User-Agent': 'OpenLP/2.1-bzr2345 Linux/4.12.0-1-amd64; '}
|
||||||
|
mock_requests.get.assert_called_once_with(expected_download_url, headers=expected_headers)
|
||||||
|
mock_new_version.emit.assert_called_once_with('2.4.6')
|
||||||
|
mock_quit.emit.assert_called_once_with()
|
||||||
|
|
||||||
|
|
||||||
|
@patch('openlp.core.version.platform')
|
||||||
|
@patch('openlp.core.version.requests')
|
||||||
|
def test_worker_start_connection_error(mock_requests, mock_platform):
|
||||||
|
"""Test the VersionWorkder.start() method when a ConnectionError happens"""
|
||||||
|
# GIVEN: A last check date, current version, and an instance of worker
|
||||||
|
last_check_date = '1970-01-01'
|
||||||
|
current_version = {'full': '2.0', 'version': '2.0', 'build': None}
|
||||||
|
mock_platform.system.return_value = 'Linux'
|
||||||
|
mock_platform.release.return_value = '4.12.0-1-amd64'
|
||||||
|
mock_requests.get.side_effect = ConnectionError('Could not connect')
|
||||||
|
worker = VersionWorker(last_check_date, current_version)
|
||||||
|
|
||||||
|
# WHEN: The worker is run
|
||||||
|
with patch.object(worker, 'no_internet') as mocked_no_internet, \
|
||||||
|
patch.object(worker, 'quit') as mocked_quit:
|
||||||
|
worker.start()
|
||||||
|
|
||||||
|
# THEN: The check completes and the signal is emitted
|
||||||
|
expected_download_url = 'http://www.openlp.org/files/version.txt'
|
||||||
|
expected_headers = {'User-Agent': 'OpenLP/2.0 Linux/4.12.0-1-amd64; '}
|
||||||
|
mock_requests.get.assert_called_with(expected_download_url, headers=expected_headers)
|
||||||
|
assert mock_requests.get.call_count == 3
|
||||||
|
mocked_no_internet.emit.assert_called_once_with()
|
||||||
|
mocked_quit.emit.assert_called_once_with()
|
||||||
|
|
||||||
|
|
||||||
|
@patch('openlp.core.version.Settings')
|
||||||
|
def test_update_check_date(MockSettings):
|
||||||
|
"""Test that the update_check_date() function writes the correct date"""
|
||||||
|
# GIVEN: A mocked Settings object
|
||||||
|
mocked_settings = MagicMock()
|
||||||
|
MockSettings.return_value = mocked_settings
|
||||||
|
|
||||||
|
# WHEN: update_check_date() is called
|
||||||
|
update_check_date()
|
||||||
|
|
||||||
|
# THEN: The correct date should have been saved
|
||||||
|
mocked_settings.setValue.assert_called_once_with('core/last version test', date.today().strftime('%Y-%m-%d'))
|
||||||
|
|
||||||
|
|
||||||
|
@patch('openlp.core.version.Settings')
|
||||||
|
@patch('openlp.core.version.run_thread')
|
||||||
|
def test_check_for_update(mocked_run_thread, MockSettings):
|
||||||
|
"""Test the check_for_update() function"""
|
||||||
|
# GIVEN: A mocked settings object
|
||||||
|
mocked_settings = MagicMock()
|
||||||
|
mocked_settings.value.return_value = '1970-01-01'
|
||||||
|
MockSettings.return_value = mocked_settings
|
||||||
|
|
||||||
|
# WHEN: check_for_update() is called
|
||||||
|
check_for_update(MagicMock())
|
||||||
|
|
||||||
|
# THEN: The right things should have been called and a thread set in motion
|
||||||
|
assert mocked_run_thread.call_count == 1
|
||||||
|
|
||||||
|
|
||||||
|
@patch('openlp.core.version.Settings')
|
||||||
|
@patch('openlp.core.version.run_thread')
|
||||||
|
def test_check_for_update_skipped(mocked_run_thread, MockSettings):
|
||||||
|
"""Test that the check_for_update() function skips running if it already ran today"""
|
||||||
|
# GIVEN: A mocked settings object
|
||||||
|
mocked_settings = MagicMock()
|
||||||
|
mocked_settings.value.return_value = date.today().strftime('%Y-%m-%d')
|
||||||
|
MockSettings.return_value = mocked_settings
|
||||||
|
|
||||||
|
# WHEN: check_for_update() is called
|
||||||
|
check_for_update(MagicMock())
|
||||||
|
|
||||||
|
# THEN: The right things should have been called and a thread set in motion
|
||||||
|
assert mocked_run_thread.call_count == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_version_dev_version():
|
||||||
|
"""Test the get_version() function"""
|
||||||
|
# GIVEN: We're in dev mode
|
||||||
|
with patch.object(sys, 'argv', ['--dev-version']), \
|
||||||
|
patch('openlp.core.version.APPLICATION_VERSION', None):
|
||||||
|
# WHEN: get_version() is run
|
||||||
|
version = get_version()
|
||||||
|
|
||||||
|
# THEN: version is something
|
||||||
|
assert version
|
|
@ -70,7 +70,7 @@ class TestWSServer(TestCase, TestMixin):
|
||||||
"""
|
"""
|
||||||
# GIVEN: A new httpserver
|
# GIVEN: A new httpserver
|
||||||
# WHEN: I start the server
|
# WHEN: I start the server
|
||||||
server = WebSocketServer()
|
WebSocketServer()
|
||||||
|
|
||||||
# THEN: the api environment should have been created
|
# THEN: the api environment should have been created
|
||||||
self.assertEquals(1, mock_qthread.call_count, 'The qthread should have been called once')
|
self.assertEquals(1, mock_qthread.call_count, 'The qthread should have been called once')
|
||||||
|
@ -93,7 +93,7 @@ class TestWSServer(TestCase, TestMixin):
|
||||||
"""
|
"""
|
||||||
Test the poll function returns the correct JSON
|
Test the poll function returns the correct JSON
|
||||||
"""
|
"""
|
||||||
# WHEN: the system is configured with a set of data
|
# GIVEN: the system is configured with a set of data
|
||||||
mocked_service_manager = MagicMock()
|
mocked_service_manager = MagicMock()
|
||||||
mocked_service_manager.service_id = 21
|
mocked_service_manager.service_id = 21
|
||||||
mocked_live_controller = MagicMock()
|
mocked_live_controller = MagicMock()
|
||||||
|
@ -105,8 +105,15 @@ class TestWSServer(TestCase, TestMixin):
|
||||||
mocked_live_controller.desktop_screen.isChecked.return_value = False
|
mocked_live_controller.desktop_screen.isChecked.return_value = False
|
||||||
Registry().register('live_controller', mocked_live_controller)
|
Registry().register('live_controller', mocked_live_controller)
|
||||||
Registry().register('service_manager', mocked_service_manager)
|
Registry().register('service_manager', mocked_service_manager)
|
||||||
|
# WHEN: The poller polls
|
||||||
|
with patch.object(self.poll, 'is_stage_active') as mocked_is_stage_active, \
|
||||||
|
patch.object(self.poll, 'is_live_active') as mocked_is_live_active, \
|
||||||
|
patch.object(self.poll, 'is_chords_active') as mocked_is_chords_active:
|
||||||
|
mocked_is_stage_active.return_value = True
|
||||||
|
mocked_is_live_active.return_value = True
|
||||||
|
mocked_is_chords_active.return_value = True
|
||||||
|
poll_json = self.poll.poll()
|
||||||
# THEN: the live json should be generated and match expected results
|
# THEN: the live json should be generated and match expected results
|
||||||
poll_json = self.poll.poll()
|
|
||||||
self.assertTrue(poll_json['results']['blank'], 'The blank return value should be True')
|
self.assertTrue(poll_json['results']['blank'], 'The blank return value should be True')
|
||||||
self.assertFalse(poll_json['results']['theme'], 'The theme return value should be False')
|
self.assertFalse(poll_json['results']['theme'], 'The theme return value should be False')
|
||||||
self.assertFalse(poll_json['results']['display'], 'The display return value should be False')
|
self.assertFalse(poll_json['results']['display'], 'The display return value should be False')
|
||||||
|
|
|
@ -24,11 +24,11 @@ Functional tests to test the AppLocation class and related methods.
|
||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
import tempfile
|
import tempfile
|
||||||
import socket
|
|
||||||
from unittest import TestCase
|
from unittest import TestCase
|
||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
from openlp.core.common.httputils import get_user_agent, get_web_page, get_url_file_size, url_get_file, ping
|
from openlp.core.common.httputils import get_user_agent, get_web_page, get_url_file_size, url_get_file
|
||||||
|
from openlp.core.common.path import Path
|
||||||
|
|
||||||
from tests.helpers.testmixin import TestMixin
|
from tests.helpers.testmixin import TestMixin
|
||||||
|
|
||||||
|
@ -67,7 +67,7 @@ class TestHttpUtils(TestCase, TestMixin):
|
||||||
"""
|
"""
|
||||||
with patch('openlp.core.common.httputils.sys') as mocked_sys:
|
with patch('openlp.core.common.httputils.sys') as mocked_sys:
|
||||||
|
|
||||||
# GIVEN: The system is Linux
|
# GIVEN: The system is Windows
|
||||||
mocked_sys.platform = 'win32'
|
mocked_sys.platform = 'win32'
|
||||||
|
|
||||||
# WHEN: We call get_user_agent()
|
# WHEN: We call get_user_agent()
|
||||||
|
@ -82,7 +82,7 @@ class TestHttpUtils(TestCase, TestMixin):
|
||||||
"""
|
"""
|
||||||
with patch('openlp.core.common.httputils.sys') as mocked_sys:
|
with patch('openlp.core.common.httputils.sys') as mocked_sys:
|
||||||
|
|
||||||
# GIVEN: The system is Linux
|
# GIVEN: The system is macOS
|
||||||
mocked_sys.platform = 'darwin'
|
mocked_sys.platform = 'darwin'
|
||||||
|
|
||||||
# WHEN: We call get_user_agent()
|
# WHEN: We call get_user_agent()
|
||||||
|
@ -97,7 +97,7 @@ class TestHttpUtils(TestCase, TestMixin):
|
||||||
"""
|
"""
|
||||||
with patch('openlp.core.common.httputils.sys') as mocked_sys:
|
with patch('openlp.core.common.httputils.sys') as mocked_sys:
|
||||||
|
|
||||||
# GIVEN: The system is Linux
|
# GIVEN: The system is something else
|
||||||
mocked_sys.platform = 'freebsd'
|
mocked_sys.platform = 'freebsd'
|
||||||
|
|
||||||
# WHEN: We call get_user_agent()
|
# WHEN: We call get_user_agent()
|
||||||
|
@ -119,182 +119,125 @@ class TestHttpUtils(TestCase, TestMixin):
|
||||||
# THEN: None should be returned
|
# THEN: None should be returned
|
||||||
self.assertIsNone(result, 'The return value of get_web_page should be None')
|
self.assertIsNone(result, 'The return value of get_web_page should be None')
|
||||||
|
|
||||||
def test_get_web_page(self):
|
@patch('openlp.core.common.httputils.requests')
|
||||||
|
@patch('openlp.core.common.httputils.get_user_agent')
|
||||||
|
@patch('openlp.core.common.httputils.Registry')
|
||||||
|
def test_get_web_page(self, MockRegistry, mocked_get_user_agent, mocked_requests):
|
||||||
"""
|
"""
|
||||||
Test that the get_web_page method works correctly
|
Test that the get_web_page method works correctly
|
||||||
"""
|
"""
|
||||||
with patch('openlp.core.common.httputils.urllib.request.Request') as MockRequest, \
|
# GIVEN: Mocked out objects and a fake URL
|
||||||
patch('openlp.core.common.httputils.urllib.request.urlopen') as mock_urlopen, \
|
mocked_requests.get.return_value = MagicMock(text='text')
|
||||||
patch('openlp.core.common.httputils.get_user_agent') as mock_get_user_agent, \
|
mocked_get_user_agent.return_value = 'user_agent'
|
||||||
patch('openlp.core.common.Registry') as MockRegistry:
|
fake_url = 'this://is.a.fake/url'
|
||||||
# GIVEN: Mocked out objects and a fake URL
|
|
||||||
mocked_request_object = MagicMock()
|
|
||||||
MockRequest.return_value = mocked_request_object
|
|
||||||
mocked_page_object = MagicMock()
|
|
||||||
mock_urlopen.return_value = mocked_page_object
|
|
||||||
mock_get_user_agent.return_value = 'user_agent'
|
|
||||||
fake_url = 'this://is.a.fake/url'
|
|
||||||
|
|
||||||
# WHEN: The get_web_page() method is called
|
# WHEN: The get_web_page() method is called
|
||||||
returned_page = get_web_page(fake_url)
|
returned_page = get_web_page(fake_url)
|
||||||
|
|
||||||
# THEN: The correct methods are called with the correct arguments and a web page is returned
|
# THEN: The correct methods are called with the correct arguments and a web page is returned
|
||||||
MockRequest.assert_called_with(fake_url)
|
mocked_requests.get.assert_called_once_with(fake_url, headers={'User-Agent': 'user_agent'},
|
||||||
mocked_request_object.add_header.assert_called_with('User-Agent', 'user_agent')
|
proxies=None, timeout=30.0)
|
||||||
self.assertEqual(1, mocked_request_object.add_header.call_count,
|
mocked_get_user_agent.assert_called_once_with()
|
||||||
'There should only be 1 call to add_header')
|
assert MockRegistry.call_count == 0, 'The Registry() object should have never been called'
|
||||||
mock_get_user_agent.assert_called_with()
|
assert returned_page == 'text', 'The returned page should be the mock object'
|
||||||
mock_urlopen.assert_called_with(mocked_request_object, timeout=30)
|
|
||||||
mocked_page_object.geturl.assert_called_with()
|
|
||||||
self.assertEqual(0, MockRegistry.call_count, 'The Registry() object should have never been called')
|
|
||||||
self.assertEqual(mocked_page_object, returned_page, 'The returned page should be the mock object')
|
|
||||||
|
|
||||||
def test_get_web_page_with_header(self):
|
@patch('openlp.core.common.httputils.requests')
|
||||||
|
@patch('openlp.core.common.httputils.get_user_agent')
|
||||||
|
def test_get_web_page_with_header(self, mocked_get_user_agent, mocked_requests):
|
||||||
"""
|
"""
|
||||||
Test that adding a header to the call to get_web_page() adds the header to the request
|
Test that adding a header to the call to get_web_page() adds the header to the request
|
||||||
"""
|
"""
|
||||||
with patch('openlp.core.common.httputils.urllib.request.Request') as MockRequest, \
|
# GIVEN: Mocked out objects, a fake URL and a fake header
|
||||||
patch('openlp.core.common.httputils.urllib.request.urlopen') as mock_urlopen, \
|
mocked_requests.get.return_value = MagicMock(text='text')
|
||||||
patch('openlp.core.common.httputils.get_user_agent') as mock_get_user_agent:
|
mocked_get_user_agent.return_value = 'user_agent'
|
||||||
# GIVEN: Mocked out objects, a fake URL and a fake header
|
fake_url = 'this://is.a.fake/url'
|
||||||
mocked_request_object = MagicMock()
|
fake_headers = {'Fake-Header': 'fake value'}
|
||||||
MockRequest.return_value = mocked_request_object
|
|
||||||
mocked_page_object = MagicMock()
|
|
||||||
mock_urlopen.return_value = mocked_page_object
|
|
||||||
mock_get_user_agent.return_value = 'user_agent'
|
|
||||||
fake_url = 'this://is.a.fake/url'
|
|
||||||
fake_header = ('Fake-Header', 'fake value')
|
|
||||||
|
|
||||||
# WHEN: The get_web_page() method is called
|
# WHEN: The get_web_page() method is called
|
||||||
returned_page = get_web_page(fake_url, header=fake_header)
|
returned_page = get_web_page(fake_url, headers=fake_headers)
|
||||||
|
|
||||||
# THEN: The correct methods are called with the correct arguments and a web page is returned
|
# THEN: The correct methods are called with the correct arguments and a web page is returned
|
||||||
MockRequest.assert_called_with(fake_url)
|
expected_headers = dict(fake_headers)
|
||||||
mocked_request_object.add_header.assert_called_with(fake_header[0], fake_header[1])
|
expected_headers.update({'User-Agent': 'user_agent'})
|
||||||
self.assertEqual(2, mocked_request_object.add_header.call_count,
|
mocked_requests.get.assert_called_once_with(fake_url, headers=expected_headers,
|
||||||
'There should only be 2 calls to add_header')
|
proxies=None, timeout=30.0)
|
||||||
mock_get_user_agent.assert_called_with()
|
mocked_get_user_agent.assert_called_with()
|
||||||
mock_urlopen.assert_called_with(mocked_request_object, timeout=30)
|
assert returned_page == 'text', 'The returned page should be the mock object'
|
||||||
mocked_page_object.geturl.assert_called_with()
|
|
||||||
self.assertEqual(mocked_page_object, returned_page, 'The returned page should be the mock object')
|
|
||||||
|
|
||||||
def test_get_web_page_with_user_agent_in_headers(self):
|
@patch('openlp.core.common.httputils.requests')
|
||||||
|
@patch('openlp.core.common.httputils.get_user_agent')
|
||||||
|
def test_get_web_page_with_user_agent_in_headers(self, mocked_get_user_agent, mocked_requests):
|
||||||
"""
|
"""
|
||||||
Test that adding a user agent in the header when calling get_web_page() adds that user agent to the request
|
Test that adding a user agent in the header when calling get_web_page() adds that user agent to the request
|
||||||
"""
|
"""
|
||||||
with patch('openlp.core.common.httputils.urllib.request.Request') as MockRequest, \
|
# GIVEN: Mocked out objects, a fake URL and a fake header
|
||||||
patch('openlp.core.common.httputils.urllib.request.urlopen') as mock_urlopen, \
|
mocked_requests.get.return_value = MagicMock(text='text')
|
||||||
patch('openlp.core.common.httputils.get_user_agent') as mock_get_user_agent:
|
fake_url = 'this://is.a.fake/url'
|
||||||
# GIVEN: Mocked out objects, a fake URL and a fake header
|
user_agent_headers = {'User-Agent': 'OpenLP/2.2.0'}
|
||||||
mocked_request_object = MagicMock()
|
|
||||||
MockRequest.return_value = mocked_request_object
|
|
||||||
mocked_page_object = MagicMock()
|
|
||||||
mock_urlopen.return_value = mocked_page_object
|
|
||||||
fake_url = 'this://is.a.fake/url'
|
|
||||||
user_agent_header = ('User-Agent', 'OpenLP/2.2.0')
|
|
||||||
|
|
||||||
# WHEN: The get_web_page() method is called
|
# WHEN: The get_web_page() method is called
|
||||||
returned_page = get_web_page(fake_url, header=user_agent_header)
|
returned_page = get_web_page(fake_url, headers=user_agent_headers)
|
||||||
|
|
||||||
# THEN: The correct methods are called with the correct arguments and a web page is returned
|
# THEN: The correct methods are called with the correct arguments and a web page is returned
|
||||||
MockRequest.assert_called_with(fake_url)
|
mocked_requests.get.assert_called_once_with(fake_url, headers=user_agent_headers,
|
||||||
mocked_request_object.add_header.assert_called_with(user_agent_header[0], user_agent_header[1])
|
proxies=None, timeout=30.0)
|
||||||
self.assertEqual(1, mocked_request_object.add_header.call_count,
|
assert mocked_get_user_agent.call_count == 0, 'get_user_agent() should not have been called'
|
||||||
'There should only be 1 call to add_header')
|
assert returned_page == 'text', 'The returned page should be "test"'
|
||||||
self.assertEqual(0, mock_get_user_agent.call_count, 'get_user_agent should not have been called')
|
|
||||||
mock_urlopen.assert_called_with(mocked_request_object, timeout=30)
|
|
||||||
mocked_page_object.geturl.assert_called_with()
|
|
||||||
self.assertEqual(mocked_page_object, returned_page, 'The returned page should be the mock object')
|
|
||||||
|
|
||||||
def test_get_web_page_update_openlp(self):
|
@patch('openlp.core.common.httputils.requests')
|
||||||
|
@patch('openlp.core.common.httputils.get_user_agent')
|
||||||
|
@patch('openlp.core.common.httputils.Registry')
|
||||||
|
def test_get_web_page_update_openlp(self, MockRegistry, mocked_get_user_agent, mocked_requests):
|
||||||
"""
|
"""
|
||||||
Test that passing "update_openlp" as true to get_web_page calls Registry().get('app').process_events()
|
Test that passing "update_openlp" as true to get_web_page calls Registry().get('app').process_events()
|
||||||
"""
|
"""
|
||||||
with patch('openlp.core.common.httputils.urllib.request.Request') as MockRequest, \
|
# GIVEN: Mocked out objects, a fake URL
|
||||||
patch('openlp.core.common.httputils.urllib.request.urlopen') as mock_urlopen, \
|
mocked_requests.get.return_value = MagicMock(text='text')
|
||||||
patch('openlp.core.common.httputils.get_user_agent') as mock_get_user_agent, \
|
mocked_get_user_agent.return_value = 'user_agent'
|
||||||
patch('openlp.core.common.httputils.Registry') as MockRegistry:
|
mocked_registry_object = MagicMock()
|
||||||
# GIVEN: Mocked out objects, a fake URL
|
mocked_application_object = MagicMock()
|
||||||
mocked_request_object = MagicMock()
|
mocked_registry_object.get.return_value = mocked_application_object
|
||||||
MockRequest.return_value = mocked_request_object
|
MockRegistry.return_value = mocked_registry_object
|
||||||
mocked_page_object = MagicMock()
|
fake_url = 'this://is.a.fake/url'
|
||||||
mock_urlopen.return_value = mocked_page_object
|
|
||||||
mock_get_user_agent.return_value = 'user_agent'
|
|
||||||
mocked_registry_object = MagicMock()
|
|
||||||
mocked_application_object = MagicMock()
|
|
||||||
mocked_registry_object.get.return_value = mocked_application_object
|
|
||||||
MockRegistry.return_value = mocked_registry_object
|
|
||||||
fake_url = 'this://is.a.fake/url'
|
|
||||||
|
|
||||||
# WHEN: The get_web_page() method is called
|
# WHEN: The get_web_page() method is called
|
||||||
returned_page = get_web_page(fake_url, update_openlp=True)
|
returned_page = get_web_page(fake_url, update_openlp=True)
|
||||||
|
|
||||||
# THEN: The correct methods are called with the correct arguments and a web page is returned
|
# THEN: The correct methods are called with the correct arguments and a web page is returned
|
||||||
MockRequest.assert_called_with(fake_url)
|
mocked_requests.get.assert_called_once_with(fake_url, headers={'User-Agent': 'user_agent'},
|
||||||
mocked_request_object.add_header.assert_called_with('User-Agent', 'user_agent')
|
proxies=None, timeout=30.0)
|
||||||
self.assertEqual(1, mocked_request_object.add_header.call_count,
|
mocked_get_user_agent.assert_called_once_with()
|
||||||
'There should only be 1 call to add_header')
|
mocked_registry_object.get.assert_called_with('application')
|
||||||
mock_urlopen.assert_called_with(mocked_request_object, timeout=30)
|
mocked_application_object.process_events.assert_called_with()
|
||||||
mocked_page_object.geturl.assert_called_with()
|
assert returned_page == 'text', 'The returned page should be the mock object'
|
||||||
mocked_registry_object.get.assert_called_with('application')
|
|
||||||
mocked_application_object.process_events.assert_called_with()
|
|
||||||
self.assertEqual(mocked_page_object, returned_page, 'The returned page should be the mock object')
|
|
||||||
|
|
||||||
def test_get_url_file_size(self):
|
@patch('openlp.core.common.httputils.requests')
|
||||||
|
def test_get_url_file_size(self, mocked_requests):
|
||||||
"""
|
"""
|
||||||
Test that passing "update_openlp" as true to get_web_page calls Registry().get('app').process_events()
|
Test that calling "get_url_file_size" works correctly
|
||||||
"""
|
"""
|
||||||
with patch('openlp.core.common.httputils.urllib.request.urlopen') as mock_urlopen, \
|
# GIVEN: Mocked out objects, a fake URL
|
||||||
patch('openlp.core.common.httputils.get_user_agent') as mock_get_user_agent:
|
mocked_requests.head.return_value = MagicMock(headers={'Content-Length': 100})
|
||||||
# GIVEN: Mocked out objects, a fake URL
|
fake_url = 'this://is.a.fake/url'
|
||||||
mocked_page_object = MagicMock()
|
|
||||||
mock_urlopen.return_value = mocked_page_object
|
|
||||||
mock_get_user_agent.return_value = 'user_agent'
|
|
||||||
fake_url = 'this://is.a.fake/url'
|
|
||||||
|
|
||||||
# WHEN: The get_url_file_size() method is called
|
# WHEN: The get_url_file_size() method is called
|
||||||
size = get_url_file_size(fake_url)
|
file_size = get_url_file_size(fake_url)
|
||||||
|
|
||||||
# THEN: The correct methods are called with the correct arguments and a web page is returned
|
# THEN: The correct methods are called with the correct arguments and a web page is returned
|
||||||
mock_urlopen.assert_called_with(fake_url, timeout=30)
|
mocked_requests.head.assert_called_once_with(fake_url, allow_redirects=True, timeout=30.0)
|
||||||
|
assert file_size == 100
|
||||||
|
|
||||||
@patch('openlp.core.ui.firsttimeform.urllib.request.urlopen')
|
@patch('openlp.core.common.httputils.requests')
|
||||||
def test_socket_timeout(self, mocked_urlopen):
|
def test_socket_timeout(self, mocked_requests):
|
||||||
"""
|
"""
|
||||||
Test socket timeout gets caught
|
Test socket timeout gets caught
|
||||||
"""
|
"""
|
||||||
# GIVEN: Mocked urlopen to fake a network disconnect in the middle of a download
|
# GIVEN: Mocked urlopen to fake a network disconnect in the middle of a download
|
||||||
mocked_urlopen.side_effect = socket.timeout()
|
mocked_requests.get.side_effect = IOError
|
||||||
|
|
||||||
# WHEN: Attempt to retrieve a file
|
# WHEN: Attempt to retrieve a file
|
||||||
url_get_file(MagicMock(), url='http://localhost/test', f_path=self.tempfile)
|
url_get_file(MagicMock(), url='http://localhost/test', file_path=Path(self.tempfile))
|
||||||
|
|
||||||
# THEN: socket.timeout should have been caught
|
# THEN: socket.timeout should have been caught
|
||||||
# NOTE: Test is if $tmpdir/tempfile is still there, then test fails since ftw deletes bad downloaded files
|
# NOTE: Test is if $tmpdir/tempfile is still there, then test fails since ftw deletes bad downloaded files
|
||||||
self.assertFalse(os.path.exists(self.tempfile), 'FTW url_get_file should have caught socket.timeout')
|
assert not os.path.exists(self.tempfile), 'tempfile should have been deleted'
|
||||||
|
|
||||||
def test_ping_valid(self):
|
|
||||||
"""
|
|
||||||
Test ping for OpenLP
|
|
||||||
"""
|
|
||||||
# GIVEN: a valid url to test
|
|
||||||
url = "openlp.io"
|
|
||||||
|
|
||||||
# WHEN: Attempt to check the url exists
|
|
||||||
url_found = ping(url)
|
|
||||||
|
|
||||||
# THEN: It should be found
|
|
||||||
self.assertTrue(url_found, 'OpenLP.io is not found')
|
|
||||||
|
|
||||||
def test_ping_invalid(self):
|
|
||||||
"""
|
|
||||||
Test ping for OpenLP
|
|
||||||
"""
|
|
||||||
# GIVEN: a valid url to test
|
|
||||||
url = "trb143.io"
|
|
||||||
|
|
||||||
# WHEN: Attempt to check the url exists
|
|
||||||
url_found = ping(url)
|
|
||||||
|
|
||||||
# THEN: It should be found
|
|
||||||
self.assertFalse(url_found, 'TRB143.io is found')
|
|
||||||
|
|
|
@ -24,8 +24,209 @@ Package to test the openlp.core.common.path package.
|
||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
from unittest import TestCase
|
from unittest import TestCase
|
||||||
|
from unittest.mock import ANY, MagicMock, patch
|
||||||
|
|
||||||
from openlp.core.common.path import Path, path_to_str, str_to_path
|
from openlp.core.common.path import Path, copy, copyfile, copytree, path_to_str, replace_params, rmtree, str_to_path, \
|
||||||
|
which
|
||||||
|
|
||||||
|
|
||||||
|
class TestShutil(TestCase):
|
||||||
|
"""
|
||||||
|
Tests for the :mod:`openlp.core.common.path` module
|
||||||
|
"""
|
||||||
|
def test_replace_params_no_params(self):
|
||||||
|
"""
|
||||||
|
Test replace_params when called with and empty tuple instead of parameters to replace
|
||||||
|
"""
|
||||||
|
# GIVEN: Some test data
|
||||||
|
test_args = (1, 2)
|
||||||
|
test_kwargs = {'arg3': 3, 'arg4': 4}
|
||||||
|
test_params = tuple()
|
||||||
|
|
||||||
|
# WHEN: Calling replace_params
|
||||||
|
result_args, result_kwargs = replace_params(test_args, test_kwargs, test_params)
|
||||||
|
|
||||||
|
# THEN: The positional and keyword args should not have changed
|
||||||
|
self.assertEqual(test_args, result_args)
|
||||||
|
self.assertEqual(test_kwargs, result_kwargs)
|
||||||
|
|
||||||
|
def test_replace_params_params(self):
|
||||||
|
"""
|
||||||
|
Test replace_params when given a positional and a keyword argument to change
|
||||||
|
"""
|
||||||
|
# GIVEN: Some test data
|
||||||
|
test_args = (1, 2)
|
||||||
|
test_kwargs = {'arg3': 3, 'arg4': 4}
|
||||||
|
test_params = ((1, 'arg2', str), (2, 'arg3', str))
|
||||||
|
|
||||||
|
# WHEN: Calling replace_params
|
||||||
|
result_args, result_kwargs = replace_params(test_args, test_kwargs, test_params)
|
||||||
|
|
||||||
|
# THEN: The positional and keyword args should have have changed
|
||||||
|
self.assertEqual(result_args, (1, '2'))
|
||||||
|
self.assertEqual(result_kwargs, {'arg3': '3', 'arg4': 4})
|
||||||
|
|
||||||
|
def test_copy(self):
|
||||||
|
"""
|
||||||
|
Test :func:`openlp.core.common.path.copy`
|
||||||
|
"""
|
||||||
|
# GIVEN: A mocked `shutil.copy` which returns a test path as a string
|
||||||
|
with patch('openlp.core.common.path.shutil.copy', return_value=os.path.join('destination', 'test', 'path')) \
|
||||||
|
as mocked_shutil_copy:
|
||||||
|
|
||||||
|
# WHEN: Calling :func:`openlp.core.common.path.copy` with the src and dst parameters as Path object types
|
||||||
|
result = copy(Path('source', 'test', 'path'), Path('destination', 'test', 'path'))
|
||||||
|
|
||||||
|
# THEN: :func:`shutil.copy` should have been called with the str equivalents of the Path objects.
|
||||||
|
# :func:`openlp.core.common.path.copy` should return the str type result of calling
|
||||||
|
# :func:`shutil.copy` as a Path object.
|
||||||
|
mocked_shutil_copy.assert_called_once_with(os.path.join('source', 'test', 'path'),
|
||||||
|
os.path.join('destination', 'test', 'path'))
|
||||||
|
self.assertEqual(result, Path('destination', 'test', 'path'))
|
||||||
|
|
||||||
|
def test_copy_follow_optional_params(self):
|
||||||
|
"""
|
||||||
|
Test :func:`openlp.core.common.path.copy` when follow_symlinks is set to false
|
||||||
|
"""
|
||||||
|
# GIVEN: A mocked `shutil.copy`
|
||||||
|
with patch('openlp.core.common.path.shutil.copy', return_value='') as mocked_shutil_copy:
|
||||||
|
|
||||||
|
# WHEN: Calling :func:`openlp.core.common.path.copy` with :param:`follow_symlinks` set to False
|
||||||
|
copy(Path('source', 'test', 'path'), Path('destination', 'test', 'path'), follow_symlinks=False)
|
||||||
|
|
||||||
|
# THEN: :func:`shutil.copy` should have been called with :param:`follow_symlinks` set to false
|
||||||
|
mocked_shutil_copy.assert_called_once_with(ANY, ANY, follow_symlinks=False)
|
||||||
|
|
||||||
|
def test_copyfile(self):
|
||||||
|
"""
|
||||||
|
Test :func:`openlp.core.common.path.copyfile`
|
||||||
|
"""
|
||||||
|
# GIVEN: A mocked :func:`shutil.copyfile` which returns a test path as a string
|
||||||
|
with patch('openlp.core.common.path.shutil.copyfile',
|
||||||
|
return_value=os.path.join('destination', 'test', 'path')) as mocked_shutil_copyfile:
|
||||||
|
|
||||||
|
# WHEN: Calling :func:`openlp.core.common.path.copyfile` with the src and dst parameters as Path object
|
||||||
|
# types
|
||||||
|
result = copyfile(Path('source', 'test', 'path'), Path('destination', 'test', 'path'))
|
||||||
|
|
||||||
|
# THEN: :func:`shutil.copyfile` should have been called with the str equivalents of the Path objects.
|
||||||
|
# :func:`openlp.core.common.path.copyfile` should return the str type result of calling
|
||||||
|
# :func:`shutil.copyfile` as a Path object.
|
||||||
|
mocked_shutil_copyfile.assert_called_once_with(os.path.join('source', 'test', 'path'),
|
||||||
|
os.path.join('destination', 'test', 'path'))
|
||||||
|
self.assertEqual(result, Path('destination', 'test', 'path'))
|
||||||
|
|
||||||
|
def test_copyfile_optional_params(self):
|
||||||
|
"""
|
||||||
|
Test :func:`openlp.core.common.path.copyfile` when follow_symlinks is set to false
|
||||||
|
"""
|
||||||
|
# GIVEN: A mocked :func:`shutil.copyfile`
|
||||||
|
with patch('openlp.core.common.path.shutil.copyfile', return_value='') as mocked_shutil_copyfile:
|
||||||
|
|
||||||
|
# WHEN: Calling :func:`openlp.core.common.path.copyfile` with :param:`follow_symlinks` set to False
|
||||||
|
copyfile(Path('source', 'test', 'path'), Path('destination', 'test', 'path'), follow_symlinks=False)
|
||||||
|
|
||||||
|
# THEN: :func:`shutil.copyfile` should have been called with the optional parameters, with out any of the
|
||||||
|
# values being modified
|
||||||
|
mocked_shutil_copyfile.assert_called_once_with(ANY, ANY, follow_symlinks=False)
|
||||||
|
|
||||||
|
def test_copytree(self):
|
||||||
|
"""
|
||||||
|
Test :func:`openlp.core.common.path.copytree`
|
||||||
|
"""
|
||||||
|
# GIVEN: A mocked :func:`shutil.copytree` which returns a test path as a string
|
||||||
|
with patch('openlp.core.common.path.shutil.copytree',
|
||||||
|
return_value=os.path.join('destination', 'test', 'path')) as mocked_shutil_copytree:
|
||||||
|
|
||||||
|
# WHEN: Calling :func:`openlp.core.common.path.copytree` with the src and dst parameters as Path object
|
||||||
|
# types
|
||||||
|
result = copytree(Path('source', 'test', 'path'), Path('destination', 'test', 'path'))
|
||||||
|
|
||||||
|
# THEN: :func:`shutil.copytree` should have been called with the str equivalents of the Path objects.
|
||||||
|
# :func:`openlp.core.common.path.copytree` should return the str type result of calling
|
||||||
|
# :func:`shutil.copytree` as a Path object.
|
||||||
|
mocked_shutil_copytree.assert_called_once_with(os.path.join('source', 'test', 'path'),
|
||||||
|
os.path.join('destination', 'test', 'path'))
|
||||||
|
self.assertEqual(result, Path('destination', 'test', 'path'))
|
||||||
|
|
||||||
|
def test_copytree_optional_params(self):
|
||||||
|
"""
|
||||||
|
Test :func:`openlp.core.common.path.copytree` when optional parameters are passed
|
||||||
|
"""
|
||||||
|
# GIVEN: A mocked :func:`shutil.copytree`
|
||||||
|
with patch('openlp.core.common.path.shutil.copytree', return_value='') as mocked_shutil_copytree:
|
||||||
|
mocked_ignore = MagicMock()
|
||||||
|
mocked_copy_function = MagicMock()
|
||||||
|
|
||||||
|
# WHEN: Calling :func:`openlp.core.common.path.copytree` with the optional parameters set
|
||||||
|
copytree(Path('source', 'test', 'path'), Path('destination', 'test', 'path'), symlinks=True,
|
||||||
|
ignore=mocked_ignore, copy_function=mocked_copy_function, ignore_dangling_symlinks=True)
|
||||||
|
|
||||||
|
# THEN: :func:`shutil.copytree` should have been called with the optional parameters, with out any of the
|
||||||
|
# values being modified
|
||||||
|
mocked_shutil_copytree.assert_called_once_with(ANY, ANY, symlinks=True, ignore=mocked_ignore,
|
||||||
|
copy_function=mocked_copy_function,
|
||||||
|
ignore_dangling_symlinks=True)
|
||||||
|
|
||||||
|
def test_rmtree(self):
|
||||||
|
"""
|
||||||
|
Test :func:`rmtree`
|
||||||
|
"""
|
||||||
|
# GIVEN: A mocked :func:`shutil.rmtree`
|
||||||
|
with patch('openlp.core.common.path.shutil.rmtree', return_value=None) as mocked_shutil_rmtree:
|
||||||
|
|
||||||
|
# WHEN: Calling :func:`openlp.core.common.path.rmtree` with the path parameter as Path object type
|
||||||
|
result = rmtree(Path('test', 'path'))
|
||||||
|
|
||||||
|
# THEN: :func:`shutil.rmtree` should have been called with the str equivalents of the Path object.
|
||||||
|
mocked_shutil_rmtree.assert_called_once_with(os.path.join('test', 'path'))
|
||||||
|
self.assertIsNone(result)
|
||||||
|
|
||||||
|
def test_rmtree_optional_params(self):
|
||||||
|
"""
|
||||||
|
Test :func:`openlp.core.common.path.rmtree` when optional parameters are passed
|
||||||
|
"""
|
||||||
|
# GIVEN: A mocked :func:`shutil.rmtree`
|
||||||
|
with patch('openlp.core.common.path.shutil.rmtree', return_value='') as mocked_shutil_rmtree:
|
||||||
|
mocked_on_error = MagicMock()
|
||||||
|
|
||||||
|
# WHEN: Calling :func:`openlp.core.common.path.rmtree` with :param:`ignore_errors` set to True and
|
||||||
|
# :param:`onerror` set to a mocked object
|
||||||
|
rmtree(Path('test', 'path'), ignore_errors=True, onerror=mocked_on_error)
|
||||||
|
|
||||||
|
# THEN: :func:`shutil.rmtree` should have been called with the optional parameters, with out any of the
|
||||||
|
# values being modified
|
||||||
|
mocked_shutil_rmtree.assert_called_once_with(ANY, ignore_errors=True, onerror=mocked_on_error)
|
||||||
|
|
||||||
|
def test_which_no_command(self):
|
||||||
|
"""
|
||||||
|
Test :func:`openlp.core.common.path.which` when the command is not found.
|
||||||
|
"""
|
||||||
|
# GIVEN: A mocked :func:`shutil.which` when the command is not found.
|
||||||
|
with patch('openlp.core.common.path.shutil.which', return_value=None) as mocked_shutil_which:
|
||||||
|
|
||||||
|
# WHEN: Calling :func:`openlp.core.common.path.which` with a command that does not exist.
|
||||||
|
result = which('no_command')
|
||||||
|
|
||||||
|
# THEN: :func:`shutil.which` should have been called with the command, and :func:`which` should return None.
|
||||||
|
mocked_shutil_which.assert_called_once_with('no_command')
|
||||||
|
self.assertIsNone(result)
|
||||||
|
|
||||||
|
def test_which_command(self):
|
||||||
|
"""
|
||||||
|
Test :func:`openlp.core.common.path.which` when a command has been found.
|
||||||
|
"""
|
||||||
|
# GIVEN: A mocked :func:`shutil.which` when the command is found.
|
||||||
|
with patch('openlp.core.common.path.shutil.which',
|
||||||
|
return_value=os.path.join('path', 'to', 'command')) as mocked_shutil_which:
|
||||||
|
|
||||||
|
# WHEN: Calling :func:`openlp.core.common.path.which` with a command that exists.
|
||||||
|
result = which('command')
|
||||||
|
|
||||||
|
# THEN: :func:`shutil.which` should have been called with the command, and :func:`which` should return a
|
||||||
|
# Path object equivalent of the command path.
|
||||||
|
mocked_shutil_which.assert_called_once_with('command')
|
||||||
|
self.assertEqual(result, Path('path', 'to', 'command'))
|
||||||
|
|
||||||
|
|
||||||
class TestPath(TestCase):
|
class TestPath(TestCase):
|
||||||
|
|
|
@ -32,7 +32,7 @@ from PyQt5 import QtCore, QtGui
|
||||||
from openlp.core.common.path import Path
|
from openlp.core.common.path import Path
|
||||||
from openlp.core.lib import FormattingTags, build_icon, check_item_selected, clean_tags, compare_chord_lyric, \
|
from openlp.core.lib import FormattingTags, build_icon, check_item_selected, clean_tags, compare_chord_lyric, \
|
||||||
create_separated_list, create_thumb, expand_chords, expand_chords_for_printing, expand_tags, find_formatting_tags, \
|
create_separated_list, create_thumb, expand_chords, expand_chords_for_printing, expand_tags, find_formatting_tags, \
|
||||||
get_text_file_string, image_to_byte, replace_params, resize_image, str_to_bool, validate_thumb
|
get_text_file_string, image_to_byte, resize_image, str_to_bool, validate_thumb
|
||||||
|
|
||||||
TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', 'resources'))
|
TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', 'resources'))
|
||||||
|
|
||||||
|
@ -595,93 +595,46 @@ class TestLib(TestCase):
|
||||||
Test the validate_thumb() function when the thumbnail does not exist
|
Test the validate_thumb() function when the thumbnail does not exist
|
||||||
"""
|
"""
|
||||||
# GIVEN: A mocked out os module, with path.exists returning False, and fake paths to a file and a thumb
|
# GIVEN: A mocked out os module, with path.exists returning False, and fake paths to a file and a thumb
|
||||||
with patch('openlp.core.lib.os') as mocked_os:
|
with patch.object(Path, 'exists', return_value=False) as mocked_path_exists:
|
||||||
file_path = 'path/to/file'
|
file_path = Path('path', 'to', 'file')
|
||||||
thumb_path = 'path/to/thumb'
|
thumb_path = Path('path', 'to', 'thumb')
|
||||||
mocked_os.path.exists.return_value = False
|
|
||||||
|
|
||||||
# WHEN: we run the validate_thumb() function
|
# WHEN: we run the validate_thumb() function
|
||||||
result = validate_thumb(file_path, thumb_path)
|
result = validate_thumb(file_path, thumb_path)
|
||||||
|
|
||||||
# THEN: we should have called a few functions, and the result should be False
|
# THEN: we should have called a few functions, and the result should be False
|
||||||
mocked_os.path.exists.assert_called_with(thumb_path)
|
thumb_path.exists.assert_called_once_with()
|
||||||
assert result is False, 'The result should be False'
|
self.assertFalse(result, 'The result should be False')
|
||||||
|
|
||||||
def test_validate_thumb_file_exists_and_newer(self):
|
def test_validate_thumb_file_exists_and_newer(self):
|
||||||
"""
|
"""
|
||||||
Test the validate_thumb() function when the thumbnail exists and has a newer timestamp than the file
|
Test the validate_thumb() function when the thumbnail exists and has a newer timestamp than the file
|
||||||
"""
|
"""
|
||||||
# GIVEN: A mocked out os module, functions rigged to work for us, and fake paths to a file and a thumb
|
with patch.object(Path, 'exists'), patch.object(Path, 'stat'):
|
||||||
with patch('openlp.core.lib.os') as mocked_os:
|
# GIVEN: Mocked file_path and thumb_path which return different values fo the modified times
|
||||||
file_path = 'path/to/file'
|
file_path = MagicMock(**{'stat.return_value': MagicMock(st_mtime=10)})
|
||||||
thumb_path = 'path/to/thumb'
|
thumb_path = MagicMock(**{'exists.return_value': True, 'stat.return_value': MagicMock(st_mtime=11)})
|
||||||
file_mocked_stat = MagicMock()
|
|
||||||
file_mocked_stat.st_mtime = datetime.now()
|
|
||||||
thumb_mocked_stat = MagicMock()
|
|
||||||
thumb_mocked_stat.st_mtime = datetime.now() + timedelta(seconds=10)
|
|
||||||
mocked_os.path.exists.return_value = True
|
|
||||||
mocked_os.stat.side_effect = [file_mocked_stat, thumb_mocked_stat]
|
|
||||||
|
|
||||||
# WHEN: we run the validate_thumb() function
|
# WHEN: we run the validate_thumb() function
|
||||||
|
result = validate_thumb(file_path, thumb_path)
|
||||||
|
|
||||||
# THEN: we should have called a few functions, and the result should be True
|
# THEN: `validate_thumb` should return True
|
||||||
# mocked_os.path.exists.assert_called_with(thumb_path)
|
self.assertTrue(result)
|
||||||
|
|
||||||
def test_validate_thumb_file_exists_and_older(self):
|
def test_validate_thumb_file_exists_and_older(self):
|
||||||
"""
|
"""
|
||||||
Test the validate_thumb() function when the thumbnail exists but is older than the file
|
Test the validate_thumb() function when the thumbnail exists but is older than the file
|
||||||
"""
|
"""
|
||||||
# GIVEN: A mocked out os module, functions rigged to work for us, and fake paths to a file and a thumb
|
# GIVEN: Mocked file_path and thumb_path which return different values fo the modified times
|
||||||
with patch('openlp.core.lib.os') as mocked_os:
|
file_path = MagicMock(**{'stat.return_value': MagicMock(st_mtime=10)})
|
||||||
file_path = 'path/to/file'
|
thumb_path = MagicMock(**{'exists.return_value': True, 'stat.return_value': MagicMock(st_mtime=9)})
|
||||||
thumb_path = 'path/to/thumb'
|
|
||||||
file_mocked_stat = MagicMock()
|
|
||||||
file_mocked_stat.st_mtime = datetime.now()
|
|
||||||
thumb_mocked_stat = MagicMock()
|
|
||||||
thumb_mocked_stat.st_mtime = datetime.now() - timedelta(seconds=10)
|
|
||||||
mocked_os.path.exists.return_value = True
|
|
||||||
mocked_os.stat.side_effect = lambda fname: file_mocked_stat if fname == file_path else thumb_mocked_stat
|
|
||||||
|
|
||||||
# WHEN: we run the validate_thumb() function
|
# WHEN: we run the validate_thumb() function
|
||||||
result = validate_thumb(file_path, thumb_path)
|
result = validate_thumb(file_path, thumb_path)
|
||||||
|
|
||||||
# THEN: we should have called a few functions, and the result should be False
|
# THEN: `validate_thumb` should return False
|
||||||
mocked_os.path.exists.assert_called_with(thumb_path)
|
thumb_path.stat.assert_called_once_with()
|
||||||
mocked_os.stat.assert_any_call(file_path)
|
self.assertFalse(result, 'The result should be False')
|
||||||
mocked_os.stat.assert_any_call(thumb_path)
|
|
||||||
assert result is False, 'The result should be False'
|
|
||||||
|
|
||||||
def test_replace_params_no_params(self):
|
|
||||||
"""
|
|
||||||
Test replace_params when called with and empty tuple instead of parameters to replace
|
|
||||||
"""
|
|
||||||
# GIVEN: Some test data
|
|
||||||
test_args = (1, 2)
|
|
||||||
test_kwargs = {'arg3': 3, 'arg4': 4}
|
|
||||||
test_params = tuple()
|
|
||||||
|
|
||||||
# WHEN: Calling replace_params
|
|
||||||
result_args, result_kwargs = replace_params(test_args, test_kwargs, test_params)
|
|
||||||
|
|
||||||
# THEN: The positional and keyword args should not have changed
|
|
||||||
self.assertEqual(test_args, result_args)
|
|
||||||
self.assertEqual(test_kwargs, result_kwargs)
|
|
||||||
|
|
||||||
def test_replace_params_params(self):
|
|
||||||
"""
|
|
||||||
Test replace_params when given a positional and a keyword argument to change
|
|
||||||
"""
|
|
||||||
# GIVEN: Some test data
|
|
||||||
test_args = (1, 2)
|
|
||||||
test_kwargs = {'arg3': 3, 'arg4': 4}
|
|
||||||
test_params = ((1, 'arg2', str), (2, 'arg3', str))
|
|
||||||
|
|
||||||
# WHEN: Calling replace_params
|
|
||||||
result_args, result_kwargs = replace_params(test_args, test_kwargs, test_params)
|
|
||||||
|
|
||||||
# THEN: The positional and keyword args should have have changed
|
|
||||||
self.assertEqual(result_args, (1, '2'))
|
|
||||||
self.assertEqual(result_kwargs, {'arg3': '3', 'arg4': 4})
|
|
||||||
|
|
||||||
def test_resize_thumb(self):
|
def test_resize_thumb(self):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -111,7 +111,7 @@ class TestProjectorDBUpdate(TestCase):
|
||||||
"""
|
"""
|
||||||
Test that we can upgrade an old song db to the current schema
|
Test that we can upgrade an old song db to the current schema
|
||||||
"""
|
"""
|
||||||
# GIVEN: An old song db
|
# GIVEN: An old prjector db
|
||||||
old_db = os.path.join(TEST_RESOURCES_PATH, "projector", TEST_DB_PJLINK1)
|
old_db = os.path.join(TEST_RESOURCES_PATH, "projector", TEST_DB_PJLINK1)
|
||||||
tmp_db = os.path.join(self.tmp_folder, TEST_DB)
|
tmp_db = os.path.join(self.tmp_folder, TEST_DB)
|
||||||
shutil.copyfile(old_db, tmp_db)
|
shutil.copyfile(old_db, tmp_db)
|
||||||
|
|
|
@ -25,12 +25,13 @@ Package to test the openlp.core.lib.projector.pjlink base package.
|
||||||
from unittest import TestCase
|
from unittest import TestCase
|
||||||
from unittest.mock import call, patch, MagicMock
|
from unittest.mock import call, patch, MagicMock
|
||||||
|
|
||||||
|
from openlp.core.lib.projector.db import Projector
|
||||||
from openlp.core.lib.projector.pjlink import PJLink
|
from openlp.core.lib.projector.pjlink import PJLink
|
||||||
from openlp.core.lib.projector.constants import E_PARAMETER, ERROR_STRING, S_ON, S_CONNECTED
|
from openlp.core.lib.projector.constants import E_PARAMETER, ERROR_STRING, S_ON, S_CONNECTED
|
||||||
|
|
||||||
from tests.resources.projector.data import TEST_PIN, TEST_SALT, TEST_CONNECT_AUTHENTICATE, TEST_HASH
|
from tests.resources.projector.data import TEST_PIN, TEST_SALT, TEST_CONNECT_AUTHENTICATE, TEST_HASH, TEST1_DATA
|
||||||
|
|
||||||
pjlink_test = PJLink(name='test', ip='127.0.0.1', pin=TEST_PIN, no_poll=True)
|
pjlink_test = PJLink(Projector(**TEST1_DATA), no_poll=True)
|
||||||
|
|
||||||
|
|
||||||
class TestPJLinkBase(TestCase):
|
class TestPJLinkBase(TestCase):
|
||||||
|
|
|
@ -27,6 +27,7 @@ from unittest import TestCase
|
||||||
from unittest.mock import patch, MagicMock
|
from unittest.mock import patch, MagicMock
|
||||||
|
|
||||||
import openlp.core.lib.projector.pjlink
|
import openlp.core.lib.projector.pjlink
|
||||||
|
from openlp.core.lib.projector.db import Projector
|
||||||
from openlp.core.lib.projector.pjlink import PJLink
|
from openlp.core.lib.projector.pjlink import PJLink
|
||||||
from openlp.core.lib.projector.constants import PJLINK_ERRORS, \
|
from openlp.core.lib.projector.constants import PJLINK_ERRORS, \
|
||||||
E_AUTHENTICATION, E_PARAMETER, E_PROJECTOR, E_UNAVAILABLE, E_UNDEFINED
|
E_AUTHENTICATION, E_PARAMETER, E_PROJECTOR, E_UNAVAILABLE, E_UNDEFINED
|
||||||
|
@ -35,9 +36,10 @@ from openlp.core.lib.projector.constants import PJLINK_ERRORS, \
|
||||||
from openlp.core.lib.projector.constants import ERROR_STRING, PJLINK_ERST_DATA, PJLINK_ERST_STATUS, \
|
from openlp.core.lib.projector.constants import ERROR_STRING, PJLINK_ERST_DATA, PJLINK_ERST_STATUS, \
|
||||||
PJLINK_POWR_STATUS, PJLINK_VALID_CMD, E_WARN, E_ERROR, S_OFF, S_STANDBY, S_ON
|
PJLINK_POWR_STATUS, PJLINK_VALID_CMD, E_WARN, E_ERROR, S_OFF, S_STANDBY, S_ON
|
||||||
'''
|
'''
|
||||||
from tests.resources.projector.data import TEST_PIN
|
from tests.resources.projector.data import TEST_PIN, TEST1_DATA
|
||||||
|
|
||||||
pjlink_test = PJLink(name='test', ip='127.0.0.1', pin=TEST_PIN, no_poll=True)
|
pjlink_test = PJLink(Projector(**TEST1_DATA), pin=TEST_PIN, no_poll=True)
|
||||||
|
pjlink_test.ip = '127.0.0.1'
|
||||||
|
|
||||||
|
|
||||||
class TestPJLinkRouting(TestCase):
|
class TestPJLinkRouting(TestCase):
|
||||||
|
|
|
@ -26,15 +26,17 @@ from unittest import TestCase
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
import openlp.core.lib.projector.pjlink
|
import openlp.core.lib.projector.pjlink
|
||||||
|
from openlp.core.lib.projector.db import Projector
|
||||||
from openlp.core.lib.projector.pjlink import PJLink
|
from openlp.core.lib.projector.pjlink import PJLink
|
||||||
from openlp.core.lib.projector.constants import ERROR_STRING, PJLINK_ERST_DATA, PJLINK_ERST_STATUS, \
|
from openlp.core.lib.projector.constants import ERROR_STRING, PJLINK_ERST_DATA, PJLINK_ERST_STATUS, \
|
||||||
PJLINK_POWR_STATUS, \
|
PJLINK_POWR_STATUS, \
|
||||||
E_ERROR, E_NOT_CONNECTED, E_SOCKET_ADDRESS_NOT_AVAILABLE, E_UNKNOWN_SOCKET_ERROR, E_WARN, \
|
E_ERROR, E_NOT_CONNECTED, E_SOCKET_ADDRESS_NOT_AVAILABLE, E_UNKNOWN_SOCKET_ERROR, E_WARN, \
|
||||||
S_CONNECTED, S_OFF, S_ON, S_NOT_CONNECTED, S_CONNECTING, S_STANDBY
|
S_CONNECTED, S_OFF, S_ON, S_NOT_CONNECTED, S_CONNECTING, S_STANDBY
|
||||||
|
|
||||||
from tests.resources.projector.data import TEST_PIN
|
from tests.resources.projector.data import TEST_PIN, TEST1_DATA
|
||||||
|
|
||||||
pjlink_test = PJLink(name='test', ip='127.0.0.1', pin=TEST_PIN, no_poll=True)
|
pjlink_test = PJLink(Projector(**TEST1_DATA), pin=TEST_PIN, no_poll=True)
|
||||||
|
pjlink_test.ip = '127.0.0.1'
|
||||||
|
|
||||||
# Create a list of ERST positional data so we don't have to redo the same buildup multiple times
|
# Create a list of ERST positional data so we don't have to redo the same buildup multiple times
|
||||||
PJLINK_ERST_POSITIONS = []
|
PJLINK_ERST_POSITIONS = []
|
||||||
|
|
|
@ -22,8 +22,9 @@
|
||||||
"""
|
"""
|
||||||
Package to test the openlp.core.lib.theme package.
|
Package to test the openlp.core.lib.theme package.
|
||||||
"""
|
"""
|
||||||
from unittest import TestCase
|
|
||||||
import os
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
from unittest import TestCase
|
||||||
|
|
||||||
from openlp.core.lib.theme import Theme
|
from openlp.core.lib.theme import Theme
|
||||||
|
|
||||||
|
@ -79,16 +80,16 @@ class TestTheme(TestCase):
|
||||||
"""
|
"""
|
||||||
# GIVEN: A theme object
|
# GIVEN: A theme object
|
||||||
theme = Theme()
|
theme = Theme()
|
||||||
theme.theme_name = 'MyBeautifulTheme '
|
theme.theme_name = 'MyBeautifulTheme'
|
||||||
theme.background_filename = ' video.mp4'
|
theme.background_filename = Path('video.mp4')
|
||||||
theme.background_type = 'video'
|
theme.background_type = 'video'
|
||||||
path = os.path.expanduser('~')
|
path = Path.home()
|
||||||
|
|
||||||
# WHEN: Theme.extend_image_filename is run
|
# WHEN: Theme.extend_image_filename is run
|
||||||
theme.extend_image_filename(path)
|
theme.extend_image_filename(path)
|
||||||
|
|
||||||
# THEN: The filename of the background should be correct
|
# THEN: The filename of the background should be correct
|
||||||
expected_filename = os.path.join(path, 'MyBeautifulTheme', 'video.mp4')
|
expected_filename = path / 'MyBeautifulTheme' / 'video.mp4'
|
||||||
self.assertEqual(expected_filename, theme.background_filename)
|
self.assertEqual(expected_filename, theme.background_filename)
|
||||||
self.assertEqual('MyBeautifulTheme', theme.theme_name)
|
self.assertEqual('MyBeautifulTheme', theme.theme_name)
|
||||||
|
|
||||||
|
|
|
@ -47,13 +47,13 @@ class TestFirstTimeForm(TestCase, TestMixin):
|
||||||
# THEN: A web browser is opened
|
# THEN: A web browser is opened
|
||||||
mocked_webbrowser.open_new.assert_called_with('http://openlp.org/en/contribute')
|
mocked_webbrowser.open_new.assert_called_with('http://openlp.org/en/contribute')
|
||||||
|
|
||||||
@patch('openlp.core.ui.aboutform.get_application_version')
|
@patch('openlp.core.ui.aboutform.get_version')
|
||||||
def test_about_form_build_number(self, mocked_get_application_version):
|
def test_about_form_build_number(self, mocked_get_version):
|
||||||
"""
|
"""
|
||||||
Test that the build number is added to the about form
|
Test that the build number is added to the about form
|
||||||
"""
|
"""
|
||||||
# GIVEN: A mocked out get_application_version function
|
# GIVEN: A mocked out get_version function
|
||||||
mocked_get_application_version.return_value = {'version': '3.1.5', 'build': '3000'}
|
mocked_get_version.return_value = {'version': '3.1.5', 'build': '3000'}
|
||||||
|
|
||||||
# WHEN: The about form is created
|
# WHEN: The about form is created
|
||||||
about_form = AboutForm(None)
|
about_form = AboutForm(None)
|
||||||
|
|
|
@ -22,11 +22,11 @@
|
||||||
"""
|
"""
|
||||||
Package to test the openlp.core.ui.exeptionform package.
|
Package to test the openlp.core.ui.exeptionform package.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
from unittest import TestCase
|
from unittest import TestCase
|
||||||
from unittest.mock import mock_open, patch
|
from unittest.mock import call, patch
|
||||||
|
|
||||||
from openlp.core.common import Registry
|
from openlp.core.common import Registry
|
||||||
from openlp.core.common.path import Path
|
from openlp.core.common.path import Path
|
||||||
|
@ -53,7 +53,7 @@ MAIL_ITEM_TEXT = ('**OpenLP Bug Report**\nVersion: Trunk Test\n\n--- Details of
|
||||||
|
|
||||||
@patch("openlp.core.ui.exceptionform.Qt.qVersion")
|
@patch("openlp.core.ui.exceptionform.Qt.qVersion")
|
||||||
@patch("openlp.core.ui.exceptionform.QtGui.QDesktopServices.openUrl")
|
@patch("openlp.core.ui.exceptionform.QtGui.QDesktopServices.openUrl")
|
||||||
@patch("openlp.core.ui.exceptionform.get_application_version")
|
@patch("openlp.core.ui.exceptionform.get_version")
|
||||||
@patch("openlp.core.ui.exceptionform.sqlalchemy")
|
@patch("openlp.core.ui.exceptionform.sqlalchemy")
|
||||||
@patch("openlp.core.ui.exceptionform.bs4")
|
@patch("openlp.core.ui.exceptionform.bs4")
|
||||||
@patch("openlp.core.ui.exceptionform.etree")
|
@patch("openlp.core.ui.exceptionform.etree")
|
||||||
|
@ -64,18 +64,10 @@ class TestExceptionForm(TestMixin, TestCase):
|
||||||
"""
|
"""
|
||||||
Test functionality of exception form functions
|
Test functionality of exception form functions
|
||||||
"""
|
"""
|
||||||
def __method_template_for_class_patches(self,
|
def __method_template_for_class_patches(self, __PLACEHOLDER_FOR_LOCAL_METHOD_PATCH_DECORATORS_GO_HERE__,
|
||||||
__PLACEHOLDER_FOR_LOCAL_METHOD_PATCH_DECORATORS_GO_HERE__,
|
mocked_python_version, mocked_platform, mocked_is_linux,
|
||||||
mocked_python_version,
|
mocked_etree, mocked_bs4, mocked_sqlalchemy, mocked_get_version,
|
||||||
mocked_platform,
|
mocked_openlurl, mocked_qversion):
|
||||||
mocked_is_linux,
|
|
||||||
mocked_etree,
|
|
||||||
mocked_bs4,
|
|
||||||
mocked_sqlalchemy,
|
|
||||||
mocked_application_version,
|
|
||||||
mocked_openlurl,
|
|
||||||
mocked_qversion,
|
|
||||||
):
|
|
||||||
"""
|
"""
|
||||||
Template so you don't have to remember the layout of class mock options for methods
|
Template so you don't have to remember the layout of class mock options for methods
|
||||||
"""
|
"""
|
||||||
|
@ -86,7 +78,7 @@ class TestExceptionForm(TestMixin, TestCase):
|
||||||
mocked_platform.return_value = 'Nose Test'
|
mocked_platform.return_value = 'Nose Test'
|
||||||
mocked_qversion.return_value = 'Qt5 test'
|
mocked_qversion.return_value = 'Qt5 test'
|
||||||
mocked_is_linux.return_value = False
|
mocked_is_linux.return_value = False
|
||||||
mocked_application_version.return_value = 'Trunk Test'
|
mocked_get_version.return_value = 'Trunk Test'
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.setup_application()
|
self.setup_application()
|
||||||
|
@ -103,26 +95,14 @@ class TestExceptionForm(TestMixin, TestCase):
|
||||||
os.remove(self.tempfile)
|
os.remove(self.tempfile)
|
||||||
|
|
||||||
@patch("openlp.core.ui.exceptionform.Ui_ExceptionDialog")
|
@patch("openlp.core.ui.exceptionform.Ui_ExceptionDialog")
|
||||||
@patch("openlp.core.ui.exceptionform.QtWidgets.QFileDialog")
|
@patch("openlp.core.ui.exceptionform.FileDialog")
|
||||||
@patch("openlp.core.ui.exceptionform.QtCore.QUrl")
|
@patch("openlp.core.ui.exceptionform.QtCore.QUrl")
|
||||||
@patch("openlp.core.ui.exceptionform.QtCore.QUrlQuery.addQueryItem")
|
@patch("openlp.core.ui.exceptionform.QtCore.QUrlQuery.addQueryItem")
|
||||||
@patch("openlp.core.ui.exceptionform.Qt")
|
@patch("openlp.core.ui.exceptionform.Qt")
|
||||||
def test_on_send_report_button_clicked(self,
|
def test_on_send_report_button_clicked(self, mocked_qt, mocked_add_query_item, mocked_qurl, mocked_file_dialog,
|
||||||
mocked_qt,
|
mocked_ui_exception_dialog, mocked_python_version, mocked_platform,
|
||||||
mocked_add_query_item,
|
mocked_is_linux, mocked_etree, mocked_bs4, mocked_sqlalchemy,
|
||||||
mocked_qurl,
|
mocked_get_version, mocked_openlurl, mocked_qversion):
|
||||||
mocked_file_dialog,
|
|
||||||
mocked_ui_exception_dialog,
|
|
||||||
mocked_python_version,
|
|
||||||
mocked_platform,
|
|
||||||
mocked_is_linux,
|
|
||||||
mocked_etree,
|
|
||||||
mocked_bs4,
|
|
||||||
mocked_sqlalchemy,
|
|
||||||
mocked_application_version,
|
|
||||||
mocked_openlurl,
|
|
||||||
mocked_qversion,
|
|
||||||
):
|
|
||||||
"""
|
"""
|
||||||
Test send report creates the proper system information text
|
Test send report creates the proper system information text
|
||||||
"""
|
"""
|
||||||
|
@ -134,42 +114,33 @@ class TestExceptionForm(TestMixin, TestCase):
|
||||||
mocked_platform.return_value = 'Nose Test'
|
mocked_platform.return_value = 'Nose Test'
|
||||||
mocked_qversion.return_value = 'Qt5 test'
|
mocked_qversion.return_value = 'Qt5 test'
|
||||||
mocked_is_linux.return_value = False
|
mocked_is_linux.return_value = False
|
||||||
mocked_application_version.return_value = 'Trunk Test'
|
mocked_get_version.return_value = 'Trunk Test'
|
||||||
mocked_qt.PYQT_VERSION_STR = 'PyQt5 Test'
|
mocked_qt.PYQT_VERSION_STR = 'PyQt5 Test'
|
||||||
mocked_is_linux.return_value = False
|
mocked_is_linux.return_value = False
|
||||||
mocked_application_version.return_value = 'Trunk Test'
|
mocked_get_version.return_value = 'Trunk Test'
|
||||||
|
|
||||||
test_form = exceptionform.ExceptionForm()
|
test_form = exceptionform.ExceptionForm()
|
||||||
test_form.file_attachment = None
|
test_form.file_attachment = None
|
||||||
|
|
||||||
with patch.object(test_form, '_pyuno_import') as mock_pyuno:
|
with patch.object(test_form, '_pyuno_import') as mock_pyuno, \
|
||||||
with patch.object(test_form.exception_text_edit, 'toPlainText') as mock_traceback:
|
patch.object(test_form.exception_text_edit, 'toPlainText') as mock_traceback, \
|
||||||
with patch.object(test_form.description_text_edit, 'toPlainText') as mock_description:
|
patch.object(test_form.description_text_edit, 'toPlainText') as mock_description:
|
||||||
mock_pyuno.return_value = 'UNO Bridge Test'
|
mock_pyuno.return_value = 'UNO Bridge Test'
|
||||||
mock_traceback.return_value = 'openlp: Traceback Test'
|
mock_traceback.return_value = 'openlp: Traceback Test'
|
||||||
mock_description.return_value = 'Description Test'
|
mock_description.return_value = 'Description Test'
|
||||||
|
|
||||||
# WHEN: on_save_report_button_clicked called
|
# WHEN: on_save_report_button_clicked called
|
||||||
test_form.on_send_report_button_clicked()
|
test_form.on_send_report_button_clicked()
|
||||||
|
|
||||||
# THEN: Verify strings were formatted properly
|
# THEN: Verify strings were formatted properly
|
||||||
mocked_add_query_item.assert_called_with('body', MAIL_ITEM_TEXT)
|
mocked_add_query_item.assert_called_with('body', MAIL_ITEM_TEXT)
|
||||||
|
|
||||||
@patch("openlp.core.ui.exceptionform.FileDialog.getSaveFileName")
|
@patch("openlp.core.ui.exceptionform.FileDialog.getSaveFileName")
|
||||||
@patch("openlp.core.ui.exceptionform.Qt")
|
@patch("openlp.core.ui.exceptionform.Qt")
|
||||||
def test_on_save_report_button_clicked(self,
|
def test_on_save_report_button_clicked(self, mocked_qt, mocked_save_filename, mocked_python_version,
|
||||||
mocked_qt,
|
mocked_platform, mocked_is_linux, mocked_etree, mocked_bs4,
|
||||||
mocked_save_filename,
|
mocked_sqlalchemy, mocked_get_version, mocked_openlurl,
|
||||||
mocked_python_version,
|
mocked_qversion):
|
||||||
mocked_platform,
|
|
||||||
mocked_is_linux,
|
|
||||||
mocked_etree,
|
|
||||||
mocked_bs4,
|
|
||||||
mocked_sqlalchemy,
|
|
||||||
mocked_application_version,
|
|
||||||
mocked_openlurl,
|
|
||||||
mocked_qversion,
|
|
||||||
):
|
|
||||||
"""
|
"""
|
||||||
Test save report saves the correct information to a file
|
Test save report saves the correct information to a file
|
||||||
"""
|
"""
|
||||||
|
@ -181,26 +152,25 @@ class TestExceptionForm(TestMixin, TestCase):
|
||||||
mocked_qversion.return_value = 'Qt5 test'
|
mocked_qversion.return_value = 'Qt5 test'
|
||||||
mocked_qt.PYQT_VERSION_STR = 'PyQt5 Test'
|
mocked_qt.PYQT_VERSION_STR = 'PyQt5 Test'
|
||||||
mocked_is_linux.return_value = False
|
mocked_is_linux.return_value = False
|
||||||
mocked_application_version.return_value = 'Trunk Test'
|
mocked_get_version.return_value = 'Trunk Test'
|
||||||
mocked_save_filename.return_value = (Path('testfile.txt'), 'filter')
|
|
||||||
|
|
||||||
test_form = exceptionform.ExceptionForm()
|
with patch.object(Path, 'open') as mocked_path_open:
|
||||||
test_form.file_attachment = None
|
test_path = Path('testfile.txt')
|
||||||
|
mocked_save_filename.return_value = test_path, 'ext'
|
||||||
|
|
||||||
with patch.object(test_form, '_pyuno_import') as mock_pyuno:
|
test_form = exceptionform.ExceptionForm()
|
||||||
with patch.object(test_form.exception_text_edit, 'toPlainText') as mock_traceback:
|
test_form.file_attachment = None
|
||||||
with patch.object(test_form.description_text_edit, 'toPlainText') as mock_description:
|
|
||||||
with patch("openlp.core.ui.exceptionform.open", mock_open(), create=True) as mocked_open:
|
|
||||||
mock_pyuno.return_value = 'UNO Bridge Test'
|
|
||||||
mock_traceback.return_value = 'openlp: Traceback Test'
|
|
||||||
mock_description.return_value = 'Description Test'
|
|
||||||
|
|
||||||
# WHEN: on_save_report_button_clicked called
|
with patch.object(test_form, '_pyuno_import') as mock_pyuno, \
|
||||||
test_form.on_save_report_button_clicked()
|
patch.object(test_form.exception_text_edit, 'toPlainText') as mock_traceback, \
|
||||||
|
patch.object(test_form.description_text_edit, 'toPlainText') as mock_description:
|
||||||
|
mock_pyuno.return_value = 'UNO Bridge Test'
|
||||||
|
mock_traceback.return_value = 'openlp: Traceback Test'
|
||||||
|
mock_description.return_value = 'Description Test'
|
||||||
|
|
||||||
|
# WHEN: on_save_report_button_clicked called
|
||||||
|
test_form.on_save_report_button_clicked()
|
||||||
|
|
||||||
# THEN: Verify proper calls to save file
|
# THEN: Verify proper calls to save file
|
||||||
# self.maxDiff = None
|
# self.maxDiff = None
|
||||||
check_text = "call().write({text})".format(text=MAIL_ITEM_TEXT.__repr__())
|
mocked_path_open.assert_has_calls([call().__enter__().write(MAIL_ITEM_TEXT)])
|
||||||
write_text = "{text}".format(text=mocked_open.mock_calls[1])
|
|
||||||
mocked_open.assert_called_with('testfile.txt', 'w')
|
|
||||||
self.assertEquals(check_text, write_text, "Saved information should match test text")
|
|
||||||
|
|
|
@ -22,9 +22,6 @@
|
||||||
"""
|
"""
|
||||||
Package to test the openlp.core.utils.__init__ package.
|
Package to test the openlp.core.utils.__init__ package.
|
||||||
"""
|
"""
|
||||||
import urllib.request
|
|
||||||
import urllib.error
|
|
||||||
import urllib.parse
|
|
||||||
from unittest import TestCase
|
from unittest import TestCase
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
@ -37,20 +34,21 @@ class TestFirstTimeWizard(TestMixin, TestCase):
|
||||||
"""
|
"""
|
||||||
Test First Time Wizard import functions
|
Test First Time Wizard import functions
|
||||||
"""
|
"""
|
||||||
def test_webpage_connection_retry(self):
|
@patch('openlp.core.common.httputils.requests')
|
||||||
|
def test_webpage_connection_retry(self, mocked_requests):
|
||||||
"""
|
"""
|
||||||
Test get_web_page will attempt CONNECTION_RETRIES+1 connections - bug 1409031
|
Test get_web_page will attempt CONNECTION_RETRIES+1 connections - bug 1409031
|
||||||
"""
|
"""
|
||||||
# GIVEN: Initial settings and mocks
|
# GIVEN: Initial settings and mocks
|
||||||
with patch.object(urllib.request, 'urlopen') as mocked_urlopen:
|
mocked_requests.get.side_effect = IOError('Unable to connect')
|
||||||
mocked_urlopen.side_effect = ConnectionError
|
|
||||||
|
|
||||||
# WHEN: A webpage is requested
|
# WHEN: A webpage is requested
|
||||||
try:
|
try:
|
||||||
get_web_page(url='http://localhost')
|
get_web_page('http://localhost')
|
||||||
except:
|
except Exception as e:
|
||||||
pass
|
assert isinstance(e, ConnectionError)
|
||||||
|
|
||||||
# THEN: urlopen should have been called CONNECTION_RETRIES + 1 count
|
# THEN: urlopen should have been called CONNECTION_RETRIES + 1 count
|
||||||
self.assertEquals(mocked_urlopen.call_count, CONNECTION_RETRIES + 1,
|
assert mocked_requests.get.call_count == CONNECTION_RETRIES, \
|
||||||
'get_web_page() should have tried {} times'.format(CONNECTION_RETRIES))
|
'get should have been called {} times, but was only called {} times'.format(
|
||||||
|
CONNECTION_RETRIES, mocked_requests.get.call_count)
|
||||||
|
|
|
@ -34,7 +34,7 @@ from openlp.core.ui.firsttimeform import FirstTimeForm
|
||||||
|
|
||||||
from tests.helpers.testmixin import TestMixin
|
from tests.helpers.testmixin import TestMixin
|
||||||
|
|
||||||
FAKE_CONFIG = b"""
|
FAKE_CONFIG = """
|
||||||
[general]
|
[general]
|
||||||
base url = http://example.com/frw/
|
base url = http://example.com/frw/
|
||||||
[songs]
|
[songs]
|
||||||
|
@ -45,7 +45,7 @@ directory = bibles
|
||||||
directory = themes
|
directory = themes
|
||||||
"""
|
"""
|
||||||
|
|
||||||
FAKE_BROKEN_CONFIG = b"""
|
FAKE_BROKEN_CONFIG = """
|
||||||
[general]
|
[general]
|
||||||
base url = http://example.com/frw/
|
base url = http://example.com/frw/
|
||||||
[songs]
|
[songs]
|
||||||
|
@ -54,7 +54,7 @@ directory = songs
|
||||||
directory = bibles
|
directory = bibles
|
||||||
"""
|
"""
|
||||||
|
|
||||||
FAKE_INVALID_CONFIG = b"""
|
FAKE_INVALID_CONFIG = """
|
||||||
<html>
|
<html>
|
||||||
<head><title>This is not a config file</title></head>
|
<head><title>This is not a config file</title></head>
|
||||||
<body>Some text</body>
|
<body>Some text</body>
|
||||||
|
@ -112,7 +112,7 @@ class TestFirstTimeForm(TestCase, TestMixin):
|
||||||
patch('openlp.core.ui.firsttimeform.Settings') as MockedSettings, \
|
patch('openlp.core.ui.firsttimeform.Settings') as MockedSettings, \
|
||||||
patch('openlp.core.ui.firsttimeform.gettempdir') as mocked_gettempdir, \
|
patch('openlp.core.ui.firsttimeform.gettempdir') as mocked_gettempdir, \
|
||||||
patch('openlp.core.ui.firsttimeform.check_directory_exists') as mocked_check_directory_exists, \
|
patch('openlp.core.ui.firsttimeform.check_directory_exists') as mocked_check_directory_exists, \
|
||||||
patch.object(frw.application, 'set_normal_cursor') as mocked_set_normal_cursor:
|
patch.object(frw.application, 'set_normal_cursor'):
|
||||||
mocked_settings = MagicMock()
|
mocked_settings = MagicMock()
|
||||||
mocked_settings.value.return_value = True
|
mocked_settings.value.return_value = True
|
||||||
MockedSettings.return_value = mocked_settings
|
MockedSettings.return_value = mocked_settings
|
||||||
|
@ -192,7 +192,7 @@ class TestFirstTimeForm(TestCase, TestMixin):
|
||||||
with patch('openlp.core.ui.firsttimeform.get_web_page') as mocked_get_web_page:
|
with patch('openlp.core.ui.firsttimeform.get_web_page') as mocked_get_web_page:
|
||||||
first_time_form = FirstTimeForm(None)
|
first_time_form = FirstTimeForm(None)
|
||||||
first_time_form.initialize(MagicMock())
|
first_time_form.initialize(MagicMock())
|
||||||
mocked_get_web_page.return_value.read.return_value = FAKE_BROKEN_CONFIG
|
mocked_get_web_page.return_value = FAKE_BROKEN_CONFIG
|
||||||
|
|
||||||
# WHEN: The First Time Wizard is downloads the config file
|
# WHEN: The First Time Wizard is downloads the config file
|
||||||
first_time_form._download_index()
|
first_time_form._download_index()
|
||||||
|
@ -208,7 +208,7 @@ class TestFirstTimeForm(TestCase, TestMixin):
|
||||||
with patch('openlp.core.ui.firsttimeform.get_web_page') as mocked_get_web_page:
|
with patch('openlp.core.ui.firsttimeform.get_web_page') as mocked_get_web_page:
|
||||||
first_time_form = FirstTimeForm(None)
|
first_time_form = FirstTimeForm(None)
|
||||||
first_time_form.initialize(MagicMock())
|
first_time_form.initialize(MagicMock())
|
||||||
mocked_get_web_page.return_value.read.return_value = FAKE_INVALID_CONFIG
|
mocked_get_web_page.return_value = FAKE_INVALID_CONFIG
|
||||||
|
|
||||||
# WHEN: The First Time Wizard is downloads the config file
|
# WHEN: The First Time Wizard is downloads the config file
|
||||||
first_time_form._download_index()
|
first_time_form._download_index()
|
||||||
|
@ -225,14 +225,13 @@ class TestFirstTimeForm(TestCase, TestMixin):
|
||||||
# GIVEN: Initial setup and mocks
|
# GIVEN: Initial setup and mocks
|
||||||
first_time_form = FirstTimeForm(None)
|
first_time_form = FirstTimeForm(None)
|
||||||
first_time_form.initialize(MagicMock())
|
first_time_form.initialize(MagicMock())
|
||||||
mocked_get_web_page.side_effect = urllib.error.HTTPError(url='http//localhost',
|
mocked_get_web_page.side_effect = ConnectionError('')
|
||||||
code=407,
|
mocked_message_box.Ok = 'OK'
|
||||||
msg='Network proxy error',
|
|
||||||
hdrs=None,
|
|
||||||
fp=None)
|
|
||||||
# WHEN: the First Time Wizard calls to get the initial configuration
|
# WHEN: the First Time Wizard calls to get the initial configuration
|
||||||
first_time_form._download_index()
|
first_time_form._download_index()
|
||||||
|
|
||||||
# THEN: the critical_error_message_box should have been called
|
# THEN: the critical_error_message_box should have been called
|
||||||
self.assertEquals(mocked_message_box.mock_calls[1][1][0], 'Network Error 407',
|
mocked_message_box.critical.assert_called_once_with(
|
||||||
'first_time_form should have caught Network Error')
|
first_time_form, 'Network Error', 'There was a network error attempting to connect to retrieve '
|
||||||
|
'initial configuration information', 'OK')
|
||||||
|
|
|
@ -27,10 +27,10 @@ from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
from PyQt5 import QtCore
|
from PyQt5 import QtCore
|
||||||
|
|
||||||
from openlp.core.common import Registry, is_macosx, Settings
|
from openlp.core.common import Registry, is_macosx
|
||||||
|
from openlp.core.common.path import Path
|
||||||
from openlp.core.lib import ScreenList, PluginManager
|
from openlp.core.lib import ScreenList, PluginManager
|
||||||
from openlp.core.ui import MainDisplay, AudioPlayer
|
from openlp.core.ui import MainDisplay, AudioPlayer
|
||||||
from openlp.core.ui.media import MediaController
|
|
||||||
from openlp.core.ui.maindisplay import TRANSPARENT_STYLESHEET, OPAQUE_STYLESHEET
|
from openlp.core.ui.maindisplay import TRANSPARENT_STYLESHEET, OPAQUE_STYLESHEET
|
||||||
|
|
||||||
from tests.helpers.testmixin import TestMixin
|
from tests.helpers.testmixin import TestMixin
|
||||||
|
@ -184,7 +184,7 @@ class TestMainDisplay(TestCase, TestMixin):
|
||||||
self.assertEqual(pyobjc_nsview.window().collectionBehavior(), NSWindowCollectionBehaviorManaged,
|
self.assertEqual(pyobjc_nsview.window().collectionBehavior(), NSWindowCollectionBehaviorManaged,
|
||||||
'Window collection behavior should be NSWindowCollectionBehaviorManaged')
|
'Window collection behavior should be NSWindowCollectionBehaviorManaged')
|
||||||
|
|
||||||
@patch(u'openlp.core.ui.maindisplay.Settings')
|
@patch('openlp.core.ui.maindisplay.Settings')
|
||||||
def test_show_display_startup_logo(self, MockedSettings):
|
def test_show_display_startup_logo(self, MockedSettings):
|
||||||
# GIVEN: Mocked show_display, setting for logo visibility
|
# GIVEN: Mocked show_display, setting for logo visibility
|
||||||
display = MagicMock()
|
display = MagicMock()
|
||||||
|
@ -204,7 +204,7 @@ class TestMainDisplay(TestCase, TestMixin):
|
||||||
# THEN: setVisible should had been called with "True"
|
# THEN: setVisible should had been called with "True"
|
||||||
main_display.setVisible.assert_called_once_with(True)
|
main_display.setVisible.assert_called_once_with(True)
|
||||||
|
|
||||||
@patch(u'openlp.core.ui.maindisplay.Settings')
|
@patch('openlp.core.ui.maindisplay.Settings')
|
||||||
def test_show_display_hide_startup_logo(self, MockedSettings):
|
def test_show_display_hide_startup_logo(self, MockedSettings):
|
||||||
# GIVEN: Mocked show_display, setting for logo visibility
|
# GIVEN: Mocked show_display, setting for logo visibility
|
||||||
display = MagicMock()
|
display = MagicMock()
|
||||||
|
@ -224,8 +224,8 @@ class TestMainDisplay(TestCase, TestMixin):
|
||||||
# THEN: setVisible should had not been called
|
# THEN: setVisible should had not been called
|
||||||
main_display.setVisible.assert_not_called()
|
main_display.setVisible.assert_not_called()
|
||||||
|
|
||||||
@patch(u'openlp.core.ui.maindisplay.Settings')
|
@patch('openlp.core.ui.maindisplay.Settings')
|
||||||
@patch(u'openlp.core.ui.maindisplay.build_html')
|
@patch('openlp.core.ui.maindisplay.build_html')
|
||||||
def test_build_html_no_video(self, MockedSettings, Mocked_build_html):
|
def test_build_html_no_video(self, MockedSettings, Mocked_build_html):
|
||||||
# GIVEN: Mocked display
|
# GIVEN: Mocked display
|
||||||
display = MagicMock()
|
display = MagicMock()
|
||||||
|
@ -252,8 +252,8 @@ class TestMainDisplay(TestCase, TestMixin):
|
||||||
self.assertEquals(main_display.media_controller.video.call_count, 0,
|
self.assertEquals(main_display.media_controller.video.call_count, 0,
|
||||||
'Media Controller video should not have been called')
|
'Media Controller video should not have been called')
|
||||||
|
|
||||||
@patch(u'openlp.core.ui.maindisplay.Settings')
|
@patch('openlp.core.ui.maindisplay.Settings')
|
||||||
@patch(u'openlp.core.ui.maindisplay.build_html')
|
@patch('openlp.core.ui.maindisplay.build_html')
|
||||||
def test_build_html_video(self, MockedSettings, Mocked_build_html):
|
def test_build_html_video(self, MockedSettings, Mocked_build_html):
|
||||||
# GIVEN: Mocked display
|
# GIVEN: Mocked display
|
||||||
display = MagicMock()
|
display = MagicMock()
|
||||||
|
@ -270,7 +270,7 @@ class TestMainDisplay(TestCase, TestMixin):
|
||||||
service_item.theme_data = MagicMock()
|
service_item.theme_data = MagicMock()
|
||||||
service_item.theme_data.background_type = 'video'
|
service_item.theme_data.background_type = 'video'
|
||||||
service_item.theme_data.theme_name = 'name'
|
service_item.theme_data.theme_name = 'name'
|
||||||
service_item.theme_data.background_filename = 'background_filename'
|
service_item.theme_data.background_filename = Path('background_filename')
|
||||||
mocked_plugin = MagicMock()
|
mocked_plugin = MagicMock()
|
||||||
display.plugin_manager = PluginManager()
|
display.plugin_manager = PluginManager()
|
||||||
display.plugin_manager.plugins = [mocked_plugin]
|
display.plugin_manager.plugins = [mocked_plugin]
|
||||||
|
|
|
@ -49,5 +49,5 @@ class TestThemeManager(TestCase):
|
||||||
self.instance.on_image_path_edit_path_changed(Path('/', 'new', 'pat.h'))
|
self.instance.on_image_path_edit_path_changed(Path('/', 'new', 'pat.h'))
|
||||||
|
|
||||||
# THEN: The theme background file should be set and `set_background_page_values` should have been called
|
# THEN: The theme background file should be set and `set_background_page_values` should have been called
|
||||||
self.assertEqual(self.instance.theme.background_filename, '/new/pat.h')
|
self.assertEqual(self.instance.theme.background_filename, Path('/', 'new', 'pat.h'))
|
||||||
mocked_set_background_page_values.assert_called_once_with()
|
mocked_set_background_page_values.assert_called_once_with()
|
||||||
|
|
|
@ -30,8 +30,9 @@ from unittest.mock import ANY, MagicMock, patch
|
||||||
|
|
||||||
from PyQt5 import QtWidgets
|
from PyQt5 import QtWidgets
|
||||||
|
|
||||||
from openlp.core.ui import ThemeManager
|
|
||||||
from openlp.core.common import Registry
|
from openlp.core.common import Registry
|
||||||
|
from openlp.core.common.path import Path
|
||||||
|
from openlp.core.ui import ThemeManager
|
||||||
|
|
||||||
from tests.utils.constants import TEST_RESOURCES_PATH
|
from tests.utils.constants import TEST_RESOURCES_PATH
|
||||||
|
|
||||||
|
@ -57,13 +58,13 @@ class TestThemeManager(TestCase):
|
||||||
"""
|
"""
|
||||||
# GIVEN: A new ThemeManager instance.
|
# GIVEN: A new ThemeManager instance.
|
||||||
theme_manager = ThemeManager()
|
theme_manager = ThemeManager()
|
||||||
theme_manager.path = os.path.join(TEST_RESOURCES_PATH, 'themes')
|
theme_manager.theme_path = Path(TEST_RESOURCES_PATH, 'themes')
|
||||||
with patch('zipfile.ZipFile.__init__') as mocked_zipfile_init, \
|
with patch('zipfile.ZipFile.__init__') as mocked_zipfile_init, \
|
||||||
patch('zipfile.ZipFile.write') as mocked_zipfile_write:
|
patch('zipfile.ZipFile.write') as mocked_zipfile_write:
|
||||||
mocked_zipfile_init.return_value = None
|
mocked_zipfile_init.return_value = None
|
||||||
|
|
||||||
# WHEN: The theme is exported
|
# WHEN: The theme is exported
|
||||||
theme_manager._export_theme(os.path.join('some', 'path', 'Default.otz'), 'Default')
|
theme_manager._export_theme(Path('some', 'path', 'Default.otz'), 'Default')
|
||||||
|
|
||||||
# THEN: The zipfile should be created at the given path
|
# THEN: The zipfile should be created at the given path
|
||||||
mocked_zipfile_init.assert_called_with(os.path.join('some', 'path', 'Default.otz'), 'w')
|
mocked_zipfile_init.assert_called_with(os.path.join('some', 'path', 'Default.otz'), 'w')
|
||||||
|
@ -86,57 +87,49 @@ class TestThemeManager(TestCase):
|
||||||
"""
|
"""
|
||||||
Test that we don't try to overwrite a theme background image with itself
|
Test that we don't try to overwrite a theme background image with itself
|
||||||
"""
|
"""
|
||||||
# GIVEN: A new theme manager instance, with mocked builtins.open, shutil.copyfile,
|
# GIVEN: A new theme manager instance, with mocked builtins.open, copyfile,
|
||||||
# theme, check_directory_exists and thememanager-attributes.
|
# theme, check_directory_exists and thememanager-attributes.
|
||||||
with patch('builtins.open') as mocked_open, \
|
with patch('openlp.core.ui.thememanager.copyfile') as mocked_copyfile, \
|
||||||
patch('openlp.core.ui.thememanager.shutil.copyfile') as mocked_copyfile, \
|
|
||||||
patch('openlp.core.ui.thememanager.check_directory_exists'):
|
patch('openlp.core.ui.thememanager.check_directory_exists'):
|
||||||
mocked_open.return_value = MagicMock()
|
|
||||||
theme_manager = ThemeManager(None)
|
theme_manager = ThemeManager(None)
|
||||||
theme_manager.old_background_image = None
|
theme_manager.old_background_image = None
|
||||||
theme_manager.generate_and_save_image = MagicMock()
|
theme_manager.generate_and_save_image = MagicMock()
|
||||||
theme_manager.path = ''
|
theme_manager.theme_path = MagicMock()
|
||||||
mocked_theme = MagicMock()
|
mocked_theme = MagicMock()
|
||||||
mocked_theme.theme_name = 'themename'
|
mocked_theme.theme_name = 'themename'
|
||||||
mocked_theme.extract_formatted_xml = MagicMock()
|
mocked_theme.extract_formatted_xml = MagicMock()
|
||||||
mocked_theme.extract_formatted_xml.return_value = 'fake_theme_xml'.encode()
|
mocked_theme.extract_formatted_xml.return_value = 'fake_theme_xml'.encode()
|
||||||
|
|
||||||
# WHEN: Calling _write_theme with path to the same image, but the path written slightly different
|
# WHEN: Calling _write_theme with path to the same image, but the path written slightly different
|
||||||
file_name1 = os.path.join(TEST_RESOURCES_PATH, 'church.jpg')
|
file_name1 = Path(TEST_RESOURCES_PATH, 'church.jpg')
|
||||||
# Do replacement from end of string to avoid problems with path start
|
theme_manager._write_theme(mocked_theme, file_name1, file_name1)
|
||||||
file_name2 = file_name1[::-1].replace(os.sep, os.sep + os.sep, 2)[::-1]
|
|
||||||
theme_manager._write_theme(mocked_theme, file_name1, file_name2)
|
|
||||||
|
|
||||||
# THEN: The mocked_copyfile should not have been called
|
# THEN: The mocked_copyfile should not have been called
|
||||||
self.assertFalse(mocked_copyfile.called, 'shutil.copyfile should not be called')
|
self.assertFalse(mocked_copyfile.called, 'copyfile should not be called')
|
||||||
|
|
||||||
def test_write_theme_diff_images(self):
|
def test_write_theme_diff_images(self):
|
||||||
"""
|
"""
|
||||||
Test that we do overwrite a theme background image when a new is submitted
|
Test that we do overwrite a theme background image when a new is submitted
|
||||||
"""
|
"""
|
||||||
# GIVEN: A new theme manager instance, with mocked builtins.open, shutil.copyfile,
|
# GIVEN: A new theme manager instance, with mocked builtins.open, copyfile,
|
||||||
# theme, check_directory_exists and thememanager-attributes.
|
# theme, check_directory_exists and thememanager-attributes.
|
||||||
with patch('builtins.open') as mocked_open, \
|
with patch('openlp.core.ui.thememanager.copyfile') as mocked_copyfile, \
|
||||||
patch('openlp.core.ui.thememanager.shutil.copyfile') as mocked_copyfile, \
|
|
||||||
patch('openlp.core.ui.thememanager.check_directory_exists'):
|
patch('openlp.core.ui.thememanager.check_directory_exists'):
|
||||||
mocked_open.return_value = MagicMock()
|
|
||||||
theme_manager = ThemeManager(None)
|
theme_manager = ThemeManager(None)
|
||||||
theme_manager.old_background_image = None
|
theme_manager.old_background_image = None
|
||||||
theme_manager.generate_and_save_image = MagicMock()
|
theme_manager.generate_and_save_image = MagicMock()
|
||||||
theme_manager.path = ''
|
theme_manager.theme_path = MagicMock()
|
||||||
mocked_theme = MagicMock()
|
mocked_theme = MagicMock()
|
||||||
mocked_theme.theme_name = 'themename'
|
mocked_theme.theme_name = 'themename'
|
||||||
mocked_theme.filename = "filename"
|
mocked_theme.filename = "filename"
|
||||||
# mocked_theme.extract_formatted_xml = MagicMock()
|
|
||||||
# mocked_theme.extract_formatted_xml.return_value = 'fake_theme_xml'.encode()
|
|
||||||
|
|
||||||
# WHEN: Calling _write_theme with path to different images
|
# WHEN: Calling _write_theme with path to different images
|
||||||
file_name1 = os.path.join(TEST_RESOURCES_PATH, 'church.jpg')
|
file_name1 = Path(TEST_RESOURCES_PATH, 'church.jpg')
|
||||||
file_name2 = os.path.join(TEST_RESOURCES_PATH, 'church2.jpg')
|
file_name2 = Path(TEST_RESOURCES_PATH, 'church2.jpg')
|
||||||
theme_manager._write_theme(mocked_theme, file_name1, file_name2)
|
theme_manager._write_theme(mocked_theme, file_name1, file_name2)
|
||||||
|
|
||||||
# THEN: The mocked_copyfile should not have been called
|
# THEN: The mocked_copyfile should not have been called
|
||||||
self.assertTrue(mocked_copyfile.called, 'shutil.copyfile should be called')
|
self.assertTrue(mocked_copyfile.called, 'copyfile should be called')
|
||||||
|
|
||||||
def test_write_theme_special_char_name(self):
|
def test_write_theme_special_char_name(self):
|
||||||
"""
|
"""
|
||||||
|
@ -146,7 +139,7 @@ class TestThemeManager(TestCase):
|
||||||
theme_manager = ThemeManager(None)
|
theme_manager = ThemeManager(None)
|
||||||
theme_manager.old_background_image = None
|
theme_manager.old_background_image = None
|
||||||
theme_manager.generate_and_save_image = MagicMock()
|
theme_manager.generate_and_save_image = MagicMock()
|
||||||
theme_manager.path = self.temp_folder
|
theme_manager.theme_path = Path(self.temp_folder)
|
||||||
mocked_theme = MagicMock()
|
mocked_theme = MagicMock()
|
||||||
mocked_theme.theme_name = 'theme 愛 name'
|
mocked_theme.theme_name = 'theme 愛 name'
|
||||||
mocked_theme.export_theme.return_value = "{}"
|
mocked_theme.export_theme.return_value = "{}"
|
||||||
|
@ -208,17 +201,17 @@ class TestThemeManager(TestCase):
|
||||||
theme_manager = ThemeManager(None)
|
theme_manager = ThemeManager(None)
|
||||||
theme_manager._create_theme_from_xml = MagicMock()
|
theme_manager._create_theme_from_xml = MagicMock()
|
||||||
theme_manager.generate_and_save_image = MagicMock()
|
theme_manager.generate_and_save_image = MagicMock()
|
||||||
theme_manager.path = ''
|
theme_manager.theme_path = None
|
||||||
folder = mkdtemp()
|
folder = Path(mkdtemp())
|
||||||
theme_file = os.path.join(TEST_RESOURCES_PATH, 'themes', 'Moss_on_tree.otz')
|
theme_file = Path(TEST_RESOURCES_PATH, 'themes', 'Moss_on_tree.otz')
|
||||||
|
|
||||||
# WHEN: We try to unzip it
|
# WHEN: We try to unzip it
|
||||||
theme_manager.unzip_theme(theme_file, folder)
|
theme_manager.unzip_theme(theme_file, folder)
|
||||||
|
|
||||||
# THEN: Files should be unpacked
|
# THEN: Files should be unpacked
|
||||||
self.assertTrue(os.path.exists(os.path.join(folder, 'Moss on tree', 'Moss on tree.xml')))
|
self.assertTrue((folder / 'Moss on tree' / 'Moss on tree.xml').exists())
|
||||||
self.assertEqual(mocked_critical_error_message_box.call_count, 0, 'No errors should have happened')
|
self.assertEqual(mocked_critical_error_message_box.call_count, 0, 'No errors should have happened')
|
||||||
shutil.rmtree(folder)
|
shutil.rmtree(str(folder))
|
||||||
|
|
||||||
def test_unzip_theme_invalid_version(self):
|
def test_unzip_theme_invalid_version(self):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -58,7 +58,7 @@ class TestImageMediaItem(TestCase):
|
||||||
Test that the validate_and_load_test() method when called without a group
|
Test that the validate_and_load_test() method when called without a group
|
||||||
"""
|
"""
|
||||||
# GIVEN: A list of files
|
# GIVEN: A list of files
|
||||||
file_list = ['/path1/image1.jpg', '/path2/image2.jpg']
|
file_list = [Path('path1', 'image1.jpg'), Path('path2', 'image2.jpg')]
|
||||||
|
|
||||||
# WHEN: Calling validate_and_load with the list of files
|
# WHEN: Calling validate_and_load with the list of files
|
||||||
self.media_item.validate_and_load(file_list)
|
self.media_item.validate_and_load(file_list)
|
||||||
|
@ -66,7 +66,7 @@ class TestImageMediaItem(TestCase):
|
||||||
# THEN: load_list should have been called with the file list and None,
|
# THEN: load_list should have been called with the file list and None,
|
||||||
# the directory should have been saved to the settings
|
# the directory should have been saved to the settings
|
||||||
mocked_load_list.assert_called_once_with(file_list, None)
|
mocked_load_list.assert_called_once_with(file_list, None)
|
||||||
mocked_settings().setValue.assert_called_once_with(ANY, Path('/', 'path1'))
|
mocked_settings().setValue.assert_called_once_with(ANY, Path('path1'))
|
||||||
|
|
||||||
@patch('openlp.plugins.images.lib.mediaitem.ImageMediaItem.load_list')
|
@patch('openlp.plugins.images.lib.mediaitem.ImageMediaItem.load_list')
|
||||||
@patch('openlp.plugins.images.lib.mediaitem.Settings')
|
@patch('openlp.plugins.images.lib.mediaitem.Settings')
|
||||||
|
@ -75,7 +75,7 @@ class TestImageMediaItem(TestCase):
|
||||||
Test that the validate_and_load_test() method when called with a group
|
Test that the validate_and_load_test() method when called with a group
|
||||||
"""
|
"""
|
||||||
# GIVEN: A list of files
|
# GIVEN: A list of files
|
||||||
file_list = ['/path1/image1.jpg', '/path2/image2.jpg']
|
file_list = [Path('path1', 'image1.jpg'), Path('path2', 'image2.jpg')]
|
||||||
|
|
||||||
# WHEN: Calling validate_and_load with the list of files and a group
|
# WHEN: Calling validate_and_load with the list of files and a group
|
||||||
self.media_item.validate_and_load(file_list, 'group')
|
self.media_item.validate_and_load(file_list, 'group')
|
||||||
|
@ -83,7 +83,7 @@ class TestImageMediaItem(TestCase):
|
||||||
# THEN: load_list should have been called with the file list and the group name,
|
# THEN: load_list should have been called with the file list and the group name,
|
||||||
# the directory should have been saved to the settings
|
# the directory should have been saved to the settings
|
||||||
mocked_load_list.assert_called_once_with(file_list, 'group')
|
mocked_load_list.assert_called_once_with(file_list, 'group')
|
||||||
mocked_settings().setValue.assert_called_once_with(ANY, Path('/', 'path1'))
|
mocked_settings().setValue.assert_called_once_with(ANY, Path('path1'))
|
||||||
|
|
||||||
@patch('openlp.plugins.images.lib.mediaitem.ImageMediaItem.load_full_list')
|
@patch('openlp.plugins.images.lib.mediaitem.ImageMediaItem.load_full_list')
|
||||||
def test_save_new_images_list_empty_list(self, mocked_load_full_list):
|
def test_save_new_images_list_empty_list(self, mocked_load_full_list):
|
||||||
|
@ -107,8 +107,8 @@ class TestImageMediaItem(TestCase):
|
||||||
Test that the save_new_images_list() calls load_full_list() when reload_list is set to True
|
Test that the save_new_images_list() calls load_full_list() when reload_list is set to True
|
||||||
"""
|
"""
|
||||||
# GIVEN: A list with 1 image and a mocked out manager
|
# GIVEN: A list with 1 image and a mocked out manager
|
||||||
image_list = ['test_image.jpg']
|
image_list = [Path('test_image.jpg')]
|
||||||
ImageFilenames.filename = ''
|
ImageFilenames.file_path = None
|
||||||
self.media_item.manager = MagicMock()
|
self.media_item.manager = MagicMock()
|
||||||
|
|
||||||
# WHEN: We run save_new_images_list with reload_list=True
|
# WHEN: We run save_new_images_list with reload_list=True
|
||||||
|
@ -118,7 +118,7 @@ class TestImageMediaItem(TestCase):
|
||||||
self.assertEquals(mocked_load_full_list.call_count, 1, 'load_full_list() should have been called')
|
self.assertEquals(mocked_load_full_list.call_count, 1, 'load_full_list() should have been called')
|
||||||
|
|
||||||
# CLEANUP: Remove added attribute from ImageFilenames
|
# CLEANUP: Remove added attribute from ImageFilenames
|
||||||
delattr(ImageFilenames, 'filename')
|
delattr(ImageFilenames, 'file_path')
|
||||||
|
|
||||||
@patch('openlp.plugins.images.lib.mediaitem.ImageMediaItem.load_full_list')
|
@patch('openlp.plugins.images.lib.mediaitem.ImageMediaItem.load_full_list')
|
||||||
def test_save_new_images_list_single_image_without_reload(self, mocked_load_full_list):
|
def test_save_new_images_list_single_image_without_reload(self, mocked_load_full_list):
|
||||||
|
@ -126,7 +126,7 @@ class TestImageMediaItem(TestCase):
|
||||||
Test that the save_new_images_list() doesn't call load_full_list() when reload_list is set to False
|
Test that the save_new_images_list() doesn't call load_full_list() when reload_list is set to False
|
||||||
"""
|
"""
|
||||||
# GIVEN: A list with 1 image and a mocked out manager
|
# GIVEN: A list with 1 image and a mocked out manager
|
||||||
image_list = ['test_image.jpg']
|
image_list = [Path('test_image.jpg')]
|
||||||
self.media_item.manager = MagicMock()
|
self.media_item.manager = MagicMock()
|
||||||
|
|
||||||
# WHEN: We run save_new_images_list with reload_list=False
|
# WHEN: We run save_new_images_list with reload_list=False
|
||||||
|
@ -141,7 +141,7 @@ class TestImageMediaItem(TestCase):
|
||||||
Test that the save_new_images_list() saves all images in the list
|
Test that the save_new_images_list() saves all images in the list
|
||||||
"""
|
"""
|
||||||
# GIVEN: A list with 3 images
|
# GIVEN: A list with 3 images
|
||||||
image_list = ['test_image_1.jpg', 'test_image_2.jpg', 'test_image_3.jpg']
|
image_list = [Path('test_image_1.jpg'), Path('test_image_2.jpg'), Path('test_image_3.jpg')]
|
||||||
self.media_item.manager = MagicMock()
|
self.media_item.manager = MagicMock()
|
||||||
|
|
||||||
# WHEN: We run save_new_images_list with the list of 3 images
|
# WHEN: We run save_new_images_list with the list of 3 images
|
||||||
|
@ -157,7 +157,7 @@ class TestImageMediaItem(TestCase):
|
||||||
Test that the save_new_images_list() ignores everything in the provided list except strings
|
Test that the save_new_images_list() ignores everything in the provided list except strings
|
||||||
"""
|
"""
|
||||||
# GIVEN: A list with images and objects
|
# GIVEN: A list with images and objects
|
||||||
image_list = ['test_image_1.jpg', None, True, ImageFilenames(), 'test_image_2.jpg']
|
image_list = [Path('test_image_1.jpg'), None, True, ImageFilenames(), Path('test_image_2.jpg')]
|
||||||
self.media_item.manager = MagicMock()
|
self.media_item.manager = MagicMock()
|
||||||
|
|
||||||
# WHEN: We run save_new_images_list with the list of images and objects
|
# WHEN: We run save_new_images_list with the list of images and objects
|
||||||
|
@ -191,7 +191,7 @@ class TestImageMediaItem(TestCase):
|
||||||
ImageGroups.parent_id = 1
|
ImageGroups.parent_id = 1
|
||||||
self.media_item.manager = MagicMock()
|
self.media_item.manager = MagicMock()
|
||||||
self.media_item.manager.get_all_objects.side_effect = self._recursively_delete_group_side_effect
|
self.media_item.manager.get_all_objects.side_effect = self._recursively_delete_group_side_effect
|
||||||
self.media_item.service_path = ''
|
self.media_item.service_path = Path()
|
||||||
test_group = ImageGroups()
|
test_group = ImageGroups()
|
||||||
test_group.id = 1
|
test_group.id = 1
|
||||||
|
|
||||||
|
@ -215,13 +215,13 @@ class TestImageMediaItem(TestCase):
|
||||||
# Create some fake objects that should be removed
|
# Create some fake objects that should be removed
|
||||||
returned_object1 = ImageFilenames()
|
returned_object1 = ImageFilenames()
|
||||||
returned_object1.id = 1
|
returned_object1.id = 1
|
||||||
returned_object1.filename = '/tmp/test_file_1.jpg'
|
returned_object1.file_path = Path('/', 'tmp', 'test_file_1.jpg')
|
||||||
returned_object2 = ImageFilenames()
|
returned_object2 = ImageFilenames()
|
||||||
returned_object2.id = 2
|
returned_object2.id = 2
|
||||||
returned_object2.filename = '/tmp/test_file_2.jpg'
|
returned_object2.file_path = Path('/', 'tmp', 'test_file_2.jpg')
|
||||||
returned_object3 = ImageFilenames()
|
returned_object3 = ImageFilenames()
|
||||||
returned_object3.id = 3
|
returned_object3.id = 3
|
||||||
returned_object3.filename = '/tmp/test_file_3.jpg'
|
returned_object3.file_path = Path('/', 'tmp', 'test_file_3.jpg')
|
||||||
return [returned_object1, returned_object2, returned_object3]
|
return [returned_object1, returned_object2, returned_object3]
|
||||||
if args[1] == ImageGroups and args[2]:
|
if args[1] == ImageGroups and args[2]:
|
||||||
# Change the parent_id that is matched so we don't get into an endless loop
|
# Change the parent_id that is matched so we don't get into an endless loop
|
||||||
|
@ -243,9 +243,9 @@ class TestImageMediaItem(TestCase):
|
||||||
test_image = ImageFilenames()
|
test_image = ImageFilenames()
|
||||||
test_image.id = 1
|
test_image.id = 1
|
||||||
test_image.group_id = 1
|
test_image.group_id = 1
|
||||||
test_image.filename = 'imagefile.png'
|
test_image.file_path = Path('imagefile.png')
|
||||||
self.media_item.manager = MagicMock()
|
self.media_item.manager = MagicMock()
|
||||||
self.media_item.service_path = ''
|
self.media_item.service_path = Path()
|
||||||
self.media_item.list_view = MagicMock()
|
self.media_item.list_view = MagicMock()
|
||||||
mocked_row_item = MagicMock()
|
mocked_row_item = MagicMock()
|
||||||
mocked_row_item.data.return_value = test_image
|
mocked_row_item.data.return_value = test_image
|
||||||
|
@ -265,13 +265,13 @@ class TestImageMediaItem(TestCase):
|
||||||
# GIVEN: An ImageFilenames that already exists in the database
|
# GIVEN: An ImageFilenames that already exists in the database
|
||||||
image_file = ImageFilenames()
|
image_file = ImageFilenames()
|
||||||
image_file.id = 1
|
image_file.id = 1
|
||||||
image_file.filename = '/tmp/test_file_1.jpg'
|
image_file.file_path = Path('/', 'tmp', 'test_file_1.jpg')
|
||||||
self.media_item.manager = MagicMock()
|
self.media_item.manager = MagicMock()
|
||||||
self.media_item.manager.get_object_filtered.return_value = image_file
|
self.media_item.manager.get_object_filtered.return_value = image_file
|
||||||
ImageFilenames.filename = ''
|
ImageFilenames.file_path = None
|
||||||
|
|
||||||
# WHEN: create_item_from_id() is called
|
# WHEN: create_item_from_id() is called
|
||||||
item = self.media_item.create_item_from_id(1)
|
item = self.media_item.create_item_from_id('1')
|
||||||
|
|
||||||
# THEN: A QTreeWidgetItem should be created with the above model object as it's data
|
# THEN: A QTreeWidgetItem should be created with the above model object as it's data
|
||||||
self.assertIsInstance(item, QtWidgets.QTreeWidgetItem)
|
self.assertIsInstance(item, QtWidgets.QTreeWidgetItem)
|
||||||
|
@ -279,4 +279,4 @@ class TestImageMediaItem(TestCase):
|
||||||
item_data = item.data(0, QtCore.Qt.UserRole)
|
item_data = item.data(0, QtCore.Qt.UserRole)
|
||||||
self.assertIsInstance(item_data, ImageFilenames)
|
self.assertIsInstance(item_data, ImageFilenames)
|
||||||
self.assertEqual(1, item_data.id)
|
self.assertEqual(1, item_data.id)
|
||||||
self.assertEqual('/tmp/test_file_1.jpg', item_data.filename)
|
self.assertEqual(Path('/', 'tmp', 'test_file_1.jpg'), item_data.file_path)
|
||||||
|
|
|
@ -0,0 +1,83 @@
|
||||||
|
# -*- 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 #
|
||||||
|
###############################################################################
|
||||||
|
"""
|
||||||
|
This module contains tests for the lib submodule of the Images plugin.
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
from tempfile import mkdtemp
|
||||||
|
from unittest import TestCase
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from openlp.core.common import AppLocation, Settings
|
||||||
|
from openlp.core.common.path import Path
|
||||||
|
from openlp.core.lib.db import Manager
|
||||||
|
from openlp.plugins.images.lib import upgrade
|
||||||
|
from openlp.plugins.images.lib.db import ImageFilenames, init_schema
|
||||||
|
|
||||||
|
from tests.helpers.testmixin import TestMixin
|
||||||
|
from tests.utils.constants import TEST_RESOURCES_PATH
|
||||||
|
|
||||||
|
__default_settings__ = {
|
||||||
|
'images/db type': 'sqlite',
|
||||||
|
'images/background color': '#000000',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class TestImageDBUpgrade(TestCase, TestMixin):
|
||||||
|
"""
|
||||||
|
Test that the image database is upgraded correctly
|
||||||
|
"""
|
||||||
|
def setUp(self):
|
||||||
|
self.build_settings()
|
||||||
|
Settings().extend_default_settings(__default_settings__)
|
||||||
|
self.tmp_folder = mkdtemp()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
"""
|
||||||
|
Delete all the C++ objects at the end so that we don't have a segfault
|
||||||
|
"""
|
||||||
|
self.destroy_settings()
|
||||||
|
# Ignore errors since windows can have problems with locked files
|
||||||
|
shutil.rmtree(self.tmp_folder, ignore_errors=True)
|
||||||
|
|
||||||
|
def test_image_filenames_table(self):
|
||||||
|
"""
|
||||||
|
Test that the ImageFilenames table is correctly upgraded to the latest version
|
||||||
|
"""
|
||||||
|
# GIVEN: An unversioned image database
|
||||||
|
temp_db_name = os.path.join(self.tmp_folder, 'image-v0.sqlite')
|
||||||
|
shutil.copyfile(os.path.join(TEST_RESOURCES_PATH, 'images', 'image-v0.sqlite'), temp_db_name)
|
||||||
|
|
||||||
|
with patch.object(AppLocation, 'get_data_path', return_value=Path('/', 'test', 'dir')):
|
||||||
|
# WHEN: Initalising the database manager
|
||||||
|
manager = Manager('images', init_schema, db_file_path=temp_db_name, upgrade_mod=upgrade)
|
||||||
|
|
||||||
|
# THEN: The database should have been upgraded and image_filenames.file_path should return Path objects
|
||||||
|
upgraded_results = manager.get_all_objects(ImageFilenames)
|
||||||
|
|
||||||
|
expected_result_data = {1: Path('/', 'test', 'image1.jpg'),
|
||||||
|
2: Path('/', 'test', 'dir', 'image2.jpg'),
|
||||||
|
3: Path('/', 'test', 'dir', 'subdir', 'image3.jpg')}
|
||||||
|
|
||||||
|
for result in upgraded_results:
|
||||||
|
self.assertEqual(expected_result_data[result.id], result.file_path)
|
|
@ -24,13 +24,12 @@ Functional tests to test the Impress class and related methods.
|
||||||
"""
|
"""
|
||||||
from unittest import TestCase
|
from unittest import TestCase
|
||||||
from unittest.mock import MagicMock
|
from unittest.mock import MagicMock
|
||||||
import os
|
|
||||||
import shutil
|
import shutil
|
||||||
from tempfile import mkdtemp
|
from tempfile import mkdtemp
|
||||||
|
|
||||||
from openlp.core.common import Settings
|
from openlp.core.common import Settings
|
||||||
from openlp.plugins.presentations.lib.impresscontroller import \
|
from openlp.core.common.path import Path
|
||||||
ImpressController, ImpressDocument, TextType
|
from openlp.plugins.presentations.lib.impresscontroller import ImpressController, ImpressDocument, TextType
|
||||||
from openlp.plugins.presentations.presentationplugin import __default_settings__
|
from openlp.plugins.presentations.presentationplugin import __default_settings__
|
||||||
|
|
||||||
from tests.utils.constants import TEST_RESOURCES_PATH
|
from tests.utils.constants import TEST_RESOURCES_PATH
|
||||||
|
@ -82,7 +81,7 @@ class TestImpressDocument(TestCase):
|
||||||
mocked_plugin = MagicMock()
|
mocked_plugin = MagicMock()
|
||||||
mocked_plugin.settings_section = 'presentations'
|
mocked_plugin.settings_section = 'presentations'
|
||||||
Settings().extend_default_settings(__default_settings__)
|
Settings().extend_default_settings(__default_settings__)
|
||||||
self.file_name = os.path.join(TEST_RESOURCES_PATH, 'presentations', 'test.pptx')
|
self.file_name = Path(TEST_RESOURCES_PATH, 'presentations', 'test.pptx')
|
||||||
self.ppc = ImpressController(mocked_plugin)
|
self.ppc = ImpressController(mocked_plugin)
|
||||||
self.doc = ImpressDocument(self.ppc, self.file_name)
|
self.doc = ImpressDocument(self.ppc, self.file_name)
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,7 @@ from unittest import TestCase
|
||||||
from unittest.mock import patch, MagicMock, call
|
from unittest.mock import patch, MagicMock, call
|
||||||
|
|
||||||
from openlp.core.common import Registry
|
from openlp.core.common import Registry
|
||||||
|
from openlp.core.common.path import Path
|
||||||
from openlp.plugins.presentations.lib.mediaitem import PresentationMediaItem
|
from openlp.plugins.presentations.lib.mediaitem import PresentationMediaItem
|
||||||
|
|
||||||
from tests.helpers.testmixin import TestMixin
|
from tests.helpers.testmixin import TestMixin
|
||||||
|
@ -92,17 +93,18 @@ class TestMediaItem(TestCase, TestMixin):
|
||||||
"""
|
"""
|
||||||
# GIVEN: A mocked controller, and mocked os.path.getmtime
|
# GIVEN: A mocked controller, and mocked os.path.getmtime
|
||||||
mocked_controller = MagicMock()
|
mocked_controller = MagicMock()
|
||||||
mocked_doc = MagicMock()
|
mocked_doc = MagicMock(**{'get_thumbnail_path.return_value': Path()})
|
||||||
mocked_controller.add_document.return_value = mocked_doc
|
mocked_controller.add_document.return_value = mocked_doc
|
||||||
mocked_controller.supports = ['tmp']
|
mocked_controller.supports = ['tmp']
|
||||||
self.media_item.controllers = {
|
self.media_item.controllers = {
|
||||||
'Mocked': mocked_controller
|
'Mocked': mocked_controller
|
||||||
}
|
}
|
||||||
presentation_file = 'file.tmp'
|
|
||||||
with patch('openlp.plugins.presentations.lib.mediaitem.os.path.getmtime') as mocked_getmtime, \
|
thmub_path = MagicMock(st_mtime=100)
|
||||||
patch('openlp.plugins.presentations.lib.mediaitem.os.path.exists') as mocked_exists:
|
file_path = MagicMock(st_mtime=400)
|
||||||
mocked_getmtime.side_effect = [100, 200]
|
with patch.object(Path, 'stat', side_effect=[thmub_path, file_path]), \
|
||||||
mocked_exists.return_value = True
|
patch.object(Path, 'exists', return_value=True):
|
||||||
|
presentation_file = Path('file.tmp')
|
||||||
|
|
||||||
# WHEN: calling clean_up_thumbnails
|
# WHEN: calling clean_up_thumbnails
|
||||||
self.media_item.clean_up_thumbnails(presentation_file, True)
|
self.media_item.clean_up_thumbnails(presentation_file, True)
|
||||||
|
@ -123,9 +125,8 @@ class TestMediaItem(TestCase, TestMixin):
|
||||||
self.media_item.controllers = {
|
self.media_item.controllers = {
|
||||||
'Mocked': mocked_controller
|
'Mocked': mocked_controller
|
||||||
}
|
}
|
||||||
presentation_file = 'file.tmp'
|
presentation_file = Path('file.tmp')
|
||||||
with patch('openlp.plugins.presentations.lib.mediaitem.os.path.exists') as mocked_exists:
|
with patch.object(Path, 'exists', return_value=False):
|
||||||
mocked_exists.return_value = False
|
|
||||||
|
|
||||||
# WHEN: calling clean_up_thumbnails
|
# WHEN: calling clean_up_thumbnails
|
||||||
self.media_item.clean_up_thumbnails(presentation_file, True)
|
self.media_item.clean_up_thumbnails(presentation_file, True)
|
||||||
|
|
|
@ -32,6 +32,7 @@ from PyQt5 import QtCore, QtGui
|
||||||
|
|
||||||
from openlp.plugins.presentations.lib.pdfcontroller import PdfController, PdfDocument
|
from openlp.plugins.presentations.lib.pdfcontroller import PdfController, PdfDocument
|
||||||
from openlp.core.common import Settings
|
from openlp.core.common import Settings
|
||||||
|
from openlp.core.common.path import Path
|
||||||
from openlp.core.lib import ScreenList
|
from openlp.core.lib import ScreenList
|
||||||
|
|
||||||
from tests.utils.constants import TEST_RESOURCES_PATH
|
from tests.utils.constants import TEST_RESOURCES_PATH
|
||||||
|
@ -66,8 +67,8 @@ class TestPdfController(TestCase, TestMixin):
|
||||||
self.desktop.screenGeometry.return_value = SCREEN['size']
|
self.desktop.screenGeometry.return_value = SCREEN['size']
|
||||||
self.screens = ScreenList.create(self.desktop)
|
self.screens = ScreenList.create(self.desktop)
|
||||||
Settings().extend_default_settings(__default_settings__)
|
Settings().extend_default_settings(__default_settings__)
|
||||||
self.temp_folder = mkdtemp()
|
self.temp_folder = Path(mkdtemp())
|
||||||
self.thumbnail_folder = mkdtemp()
|
self.thumbnail_folder = Path(mkdtemp())
|
||||||
self.mock_plugin = MagicMock()
|
self.mock_plugin = MagicMock()
|
||||||
self.mock_plugin.settings_section = self.temp_folder
|
self.mock_plugin.settings_section = self.temp_folder
|
||||||
|
|
||||||
|
@ -77,8 +78,8 @@ class TestPdfController(TestCase, TestMixin):
|
||||||
"""
|
"""
|
||||||
del self.screens
|
del self.screens
|
||||||
self.destroy_settings()
|
self.destroy_settings()
|
||||||
shutil.rmtree(self.thumbnail_folder)
|
shutil.rmtree(str(self.thumbnail_folder))
|
||||||
shutil.rmtree(self.temp_folder)
|
shutil.rmtree(str(self.temp_folder))
|
||||||
|
|
||||||
def test_constructor(self):
|
def test_constructor(self):
|
||||||
"""
|
"""
|
||||||
|
@ -98,7 +99,7 @@ class TestPdfController(TestCase, TestMixin):
|
||||||
Test loading of a Pdf using the PdfController
|
Test loading of a Pdf using the PdfController
|
||||||
"""
|
"""
|
||||||
# GIVEN: A Pdf-file
|
# GIVEN: A Pdf-file
|
||||||
test_file = os.path.join(TEST_RESOURCES_PATH, 'presentations', 'pdf_test1.pdf')
|
test_file = Path(TEST_RESOURCES_PATH, 'presentations', 'pdf_test1.pdf')
|
||||||
|
|
||||||
# WHEN: The Pdf is loaded
|
# WHEN: The Pdf is loaded
|
||||||
controller = PdfController(plugin=self.mock_plugin)
|
controller = PdfController(plugin=self.mock_plugin)
|
||||||
|
@ -118,7 +119,7 @@ class TestPdfController(TestCase, TestMixin):
|
||||||
Test loading of a Pdf and check size of generate pictures
|
Test loading of a Pdf and check size of generate pictures
|
||||||
"""
|
"""
|
||||||
# GIVEN: A Pdf-file
|
# GIVEN: A Pdf-file
|
||||||
test_file = os.path.join(TEST_RESOURCES_PATH, 'presentations', 'pdf_test1.pdf')
|
test_file = Path(TEST_RESOURCES_PATH, 'presentations', 'pdf_test1.pdf')
|
||||||
|
|
||||||
# WHEN: The Pdf is loaded
|
# WHEN: The Pdf is loaded
|
||||||
controller = PdfController(plugin=self.mock_plugin)
|
controller = PdfController(plugin=self.mock_plugin)
|
||||||
|
@ -131,7 +132,7 @@ class TestPdfController(TestCase, TestMixin):
|
||||||
|
|
||||||
# THEN: The load should succeed and pictures should be created and have been scales to fit the screen
|
# THEN: The load should succeed and pictures should be created and have been scales to fit the screen
|
||||||
self.assertTrue(loaded, 'The loading of the PDF should succeed.')
|
self.assertTrue(loaded, 'The loading of the PDF should succeed.')
|
||||||
image = QtGui.QImage(os.path.join(self.temp_folder, 'pdf_test1.pdf', 'mainslide001.png'))
|
image = QtGui.QImage(os.path.join(str(self.temp_folder), 'pdf_test1.pdf', 'mainslide001.png'))
|
||||||
# Based on the converter used the resolution will differ a bit
|
# Based on the converter used the resolution will differ a bit
|
||||||
if controller.gsbin:
|
if controller.gsbin:
|
||||||
self.assertEqual(760, image.height(), 'The height should be 760')
|
self.assertEqual(760, image.height(), 'The height should be 760')
|
||||||
|
|
|
@ -22,7 +22,6 @@
|
||||||
"""
|
"""
|
||||||
This module contains tests for the pptviewcontroller module of the Presentations plugin.
|
This module contains tests for the pptviewcontroller module of the Presentations plugin.
|
||||||
"""
|
"""
|
||||||
import os
|
|
||||||
import shutil
|
import shutil
|
||||||
from tempfile import mkdtemp
|
from tempfile import mkdtemp
|
||||||
from unittest import TestCase
|
from unittest import TestCase
|
||||||
|
@ -30,6 +29,7 @@ from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
from openlp.plugins.presentations.lib.pptviewcontroller import PptviewDocument, PptviewController
|
from openlp.plugins.presentations.lib.pptviewcontroller import PptviewDocument, PptviewController
|
||||||
from openlp.core.common import is_win
|
from openlp.core.common import is_win
|
||||||
|
from openlp.core.common.path import Path
|
||||||
|
|
||||||
from tests.helpers.testmixin import TestMixin
|
from tests.helpers.testmixin import TestMixin
|
||||||
from tests.utils.constants import TEST_RESOURCES_PATH
|
from tests.utils.constants import TEST_RESOURCES_PATH
|
||||||
|
@ -184,7 +184,7 @@ class TestPptviewDocument(TestCase):
|
||||||
"""
|
"""
|
||||||
# GIVEN: mocked PresentationController.save_titles_and_notes and a pptx file
|
# GIVEN: mocked PresentationController.save_titles_and_notes and a pptx file
|
||||||
doc = PptviewDocument(self.mock_controller, self.mock_presentation)
|
doc = PptviewDocument(self.mock_controller, self.mock_presentation)
|
||||||
doc.file_path = os.path.join(TEST_RESOURCES_PATH, 'presentations', 'test.pptx')
|
doc.file_path = Path(TEST_RESOURCES_PATH, 'presentations', 'test.pptx')
|
||||||
doc.save_titles_and_notes = MagicMock()
|
doc.save_titles_and_notes = MagicMock()
|
||||||
|
|
||||||
# WHEN reading the titles and notes
|
# WHEN reading the titles and notes
|
||||||
|
@ -201,13 +201,13 @@ class TestPptviewDocument(TestCase):
|
||||||
"""
|
"""
|
||||||
# GIVEN: mocked PresentationController.save_titles_and_notes and an nonexistent file
|
# GIVEN: mocked PresentationController.save_titles_and_notes and an nonexistent file
|
||||||
with patch('builtins.open') as mocked_open, \
|
with patch('builtins.open') as mocked_open, \
|
||||||
patch('openlp.plugins.presentations.lib.pptviewcontroller.os.path.exists') as mocked_exists, \
|
patch.object(Path, 'exists') as mocked_path_exists, \
|
||||||
patch('openlp.plugins.presentations.lib.presentationcontroller.check_directory_exists') as \
|
patch('openlp.plugins.presentations.lib.presentationcontroller.check_directory_exists') as \
|
||||||
mocked_dir_exists:
|
mocked_dir_exists:
|
||||||
mocked_exists.return_value = False
|
mocked_path_exists.return_value = False
|
||||||
mocked_dir_exists.return_value = False
|
mocked_dir_exists.return_value = False
|
||||||
doc = PptviewDocument(self.mock_controller, self.mock_presentation)
|
doc = PptviewDocument(self.mock_controller, self.mock_presentation)
|
||||||
doc.file_path = 'Idontexist.pptx'
|
doc.file_path = Path('Idontexist.pptx')
|
||||||
doc.save_titles_and_notes = MagicMock()
|
doc.save_titles_and_notes = MagicMock()
|
||||||
|
|
||||||
# WHEN: Reading the titles and notes
|
# WHEN: Reading the titles and notes
|
||||||
|
@ -215,7 +215,7 @@ class TestPptviewDocument(TestCase):
|
||||||
|
|
||||||
# THEN: File existens should have been checked, and not have been opened.
|
# THEN: File existens should have been checked, and not have been opened.
|
||||||
doc.save_titles_and_notes.assert_called_once_with(None, None)
|
doc.save_titles_and_notes.assert_called_once_with(None, None)
|
||||||
mocked_exists.assert_any_call('Idontexist.pptx')
|
mocked_path_exists.assert_called_with()
|
||||||
self.assertEqual(mocked_open.call_count, 0, 'There should be no calls to open a file.')
|
self.assertEqual(mocked_open.call_count, 0, 'There should be no calls to open a file.')
|
||||||
|
|
||||||
def test_create_titles_and_notes_invalid_file(self):
|
def test_create_titles_and_notes_invalid_file(self):
|
||||||
|
@ -228,7 +228,7 @@ class TestPptviewDocument(TestCase):
|
||||||
mocked_is_zf.return_value = False
|
mocked_is_zf.return_value = False
|
||||||
mocked_open.filesize = 10
|
mocked_open.filesize = 10
|
||||||
doc = PptviewDocument(self.mock_controller, self.mock_presentation)
|
doc = PptviewDocument(self.mock_controller, self.mock_presentation)
|
||||||
doc.file_path = os.path.join(TEST_RESOURCES_PATH, 'presentations', 'test.ppt')
|
doc.file_path = Path(TEST_RESOURCES_PATH, 'presentations', 'test.ppt')
|
||||||
doc.save_titles_and_notes = MagicMock()
|
doc.save_titles_and_notes = MagicMock()
|
||||||
|
|
||||||
# WHEN: reading the titles and notes
|
# WHEN: reading the titles and notes
|
||||||
|
|
|
@ -23,9 +23,8 @@
|
||||||
Functional tests to test the PresentationController and PresentationDocument
|
Functional tests to test the PresentationController and PresentationDocument
|
||||||
classes and related methods.
|
classes and related methods.
|
||||||
"""
|
"""
|
||||||
import os
|
|
||||||
from unittest import TestCase
|
from unittest import TestCase
|
||||||
from unittest.mock import MagicMock, mock_open, patch
|
from unittest.mock import MagicMock, call, patch
|
||||||
|
|
||||||
from openlp.core.common.path import Path
|
from openlp.core.common.path import Path
|
||||||
from openlp.plugins.presentations.lib.presentationcontroller import PresentationController, PresentationDocument
|
from openlp.plugins.presentations.lib.presentationcontroller import PresentationController, PresentationDocument
|
||||||
|
@ -67,23 +66,18 @@ class TestPresentationController(TestCase):
|
||||||
Test PresentationDocument.save_titles_and_notes method with two valid lists
|
Test PresentationDocument.save_titles_and_notes method with two valid lists
|
||||||
"""
|
"""
|
||||||
# GIVEN: two lists of length==2 and a mocked open and get_thumbnail_folder
|
# GIVEN: two lists of length==2 and a mocked open and get_thumbnail_folder
|
||||||
mocked_open = mock_open()
|
with patch('openlp.plugins.presentations.lib.presentationcontroller.Path.write_text') as mocked_write_text, \
|
||||||
with patch('builtins.open', mocked_open), patch(FOLDER_TO_PATCH) as mocked_get_thumbnail_folder:
|
patch(FOLDER_TO_PATCH) as mocked_get_thumbnail_folder:
|
||||||
titles = ['uno', 'dos']
|
titles = ['uno', 'dos']
|
||||||
notes = ['one', 'two']
|
notes = ['one', 'two']
|
||||||
|
|
||||||
# WHEN: calling save_titles_and_notes
|
# WHEN: calling save_titles_and_notes
|
||||||
mocked_get_thumbnail_folder.return_value = 'test'
|
mocked_get_thumbnail_folder.return_value = Path('test')
|
||||||
self.document.save_titles_and_notes(titles, notes)
|
self.document.save_titles_and_notes(titles, notes)
|
||||||
|
|
||||||
# THEN: the last call to open should have been for slideNotes2.txt
|
# THEN: the last call to open should have been for slideNotes2.txt
|
||||||
mocked_open.assert_any_call(os.path.join('test', 'titles.txt'), mode='wt', encoding='utf-8')
|
self.assertEqual(mocked_write_text.call_count, 3, 'There should be exactly three files written')
|
||||||
mocked_open.assert_any_call(os.path.join('test', 'slideNotes1.txt'), mode='wt', encoding='utf-8')
|
mocked_write_text.assert_has_calls([call('uno\ndos'), call('one'), call('two')])
|
||||||
mocked_open.assert_any_call(os.path.join('test', 'slideNotes2.txt'), mode='wt', encoding='utf-8')
|
|
||||||
self.assertEqual(mocked_open.call_count, 3, 'There should be exactly three files opened')
|
|
||||||
mocked_open().writelines.assert_called_once_with(['uno', 'dos'])
|
|
||||||
mocked_open().write.assert_any_call('one')
|
|
||||||
mocked_open().write.assert_any_call('two')
|
|
||||||
|
|
||||||
def test_save_titles_and_notes_with_None(self):
|
def test_save_titles_and_notes_with_None(self):
|
||||||
"""
|
"""
|
||||||
|
@ -107,10 +101,11 @@ class TestPresentationController(TestCase):
|
||||||
"""
|
"""
|
||||||
# GIVEN: A mocked open, get_thumbnail_folder and exists
|
# GIVEN: A mocked open, get_thumbnail_folder and exists
|
||||||
|
|
||||||
with patch('builtins.open', mock_open(read_data='uno\ndos\n')) as mocked_open, \
|
with patch('openlp.plugins.presentations.lib.presentationcontroller.Path.read_text',
|
||||||
|
return_value='uno\ndos\n') as mocked_read_text, \
|
||||||
patch(FOLDER_TO_PATCH) as mocked_get_thumbnail_folder, \
|
patch(FOLDER_TO_PATCH) as mocked_get_thumbnail_folder, \
|
||||||
patch('openlp.plugins.presentations.lib.presentationcontroller.os.path.exists') as mocked_exists:
|
patch('openlp.plugins.presentations.lib.presentationcontroller.Path.exists') as mocked_exists:
|
||||||
mocked_get_thumbnail_folder.return_value = 'test'
|
mocked_get_thumbnail_folder.return_value = Path('test')
|
||||||
mocked_exists.return_value = True
|
mocked_exists.return_value = True
|
||||||
|
|
||||||
# WHEN: calling get_titles_and_notes
|
# WHEN: calling get_titles_and_notes
|
||||||
|
@ -121,45 +116,36 @@ class TestPresentationController(TestCase):
|
||||||
self.assertEqual(len(result_titles), 2, 'There should be two items in the titles')
|
self.assertEqual(len(result_titles), 2, 'There should be two items in the titles')
|
||||||
self.assertIs(type(result_notes), list, 'result_notes should be of type list')
|
self.assertIs(type(result_notes), list, 'result_notes should be of type list')
|
||||||
self.assertEqual(len(result_notes), 2, 'There should be two items in the notes')
|
self.assertEqual(len(result_notes), 2, 'There should be two items in the notes')
|
||||||
self.assertEqual(mocked_open.call_count, 3, 'Three files should be opened')
|
self.assertEqual(mocked_read_text.call_count, 3, 'Three files should be read')
|
||||||
mocked_open.assert_any_call(os.path.join('test', 'titles.txt'), encoding='utf-8')
|
|
||||||
mocked_open.assert_any_call(os.path.join('test', 'slideNotes1.txt'), encoding='utf-8')
|
|
||||||
mocked_open.assert_any_call(os.path.join('test', 'slideNotes2.txt'), encoding='utf-8')
|
|
||||||
self.assertEqual(mocked_exists.call_count, 3, 'Three files should have been checked')
|
|
||||||
|
|
||||||
def test_get_titles_and_notes_with_file_not_found(self):
|
def test_get_titles_and_notes_with_file_not_found(self):
|
||||||
"""
|
"""
|
||||||
Test PresentationDocument.get_titles_and_notes method with file not found
|
Test PresentationDocument.get_titles_and_notes method with file not found
|
||||||
"""
|
"""
|
||||||
# GIVEN: A mocked open, get_thumbnail_folder and exists
|
# GIVEN: A mocked open, get_thumbnail_folder and exists
|
||||||
with patch('builtins.open') as mocked_open, \
|
with patch('openlp.plugins.presentations.lib.presentationcontroller.Path.read_text') as mocked_read_text, \
|
||||||
patch(FOLDER_TO_PATCH) as mocked_get_thumbnail_folder, \
|
patch(FOLDER_TO_PATCH) as mocked_get_thumbnail_folder:
|
||||||
patch('openlp.plugins.presentations.lib.presentationcontroller.os.path.exists') as mocked_exists:
|
mocked_read_text.side_effect = FileNotFoundError()
|
||||||
mocked_get_thumbnail_folder.return_value = 'test'
|
mocked_get_thumbnail_folder.return_value = Path('test')
|
||||||
mocked_exists.return_value = False
|
|
||||||
|
|
||||||
# WHEN: calling get_titles_and_notes
|
# WHEN: calling get_titles_and_notes
|
||||||
result_titles, result_notes = self.document.get_titles_and_notes()
|
result_titles, result_notes = self.document.get_titles_and_notes()
|
||||||
|
|
||||||
# THEN: it should return two empty lists
|
# THEN: it should return two empty lists
|
||||||
self.assertIs(type(result_titles), list, 'result_titles should be of type list')
|
self.assertIsInstance(result_titles, list, 'result_titles should be of type list')
|
||||||
self.assertEqual(len(result_titles), 0, 'there be no titles')
|
self.assertEqual(len(result_titles), 0, 'there be no titles')
|
||||||
self.assertIs(type(result_notes), list, 'result_notes should be a list')
|
self.assertIsInstance(result_notes, list, 'result_notes should be a list')
|
||||||
self.assertEqual(len(result_notes), 0, 'but the list should be empty')
|
self.assertEqual(len(result_notes), 0, 'but the list should be empty')
|
||||||
self.assertEqual(mocked_open.call_count, 0, 'No calls to open files')
|
|
||||||
self.assertEqual(mocked_exists.call_count, 1, 'There should be one call to file exists')
|
|
||||||
|
|
||||||
def test_get_titles_and_notes_with_file_error(self):
|
def test_get_titles_and_notes_with_file_error(self):
|
||||||
"""
|
"""
|
||||||
Test PresentationDocument.get_titles_and_notes method with file errors
|
Test PresentationDocument.get_titles_and_notes method with file errors
|
||||||
"""
|
"""
|
||||||
# GIVEN: A mocked open, get_thumbnail_folder and exists
|
# GIVEN: A mocked open, get_thumbnail_folder and exists
|
||||||
with patch('builtins.open') as mocked_open, \
|
with patch('openlp.plugins.presentations.lib.presentationcontroller.Path.read_text') as mocked_read_text, \
|
||||||
patch(FOLDER_TO_PATCH) as mocked_get_thumbnail_folder, \
|
patch(FOLDER_TO_PATCH) as mocked_get_thumbnail_folder:
|
||||||
patch('openlp.plugins.presentations.lib.presentationcontroller.os.path.exists') as mocked_exists:
|
mocked_read_text.side_effect = IOError()
|
||||||
mocked_get_thumbnail_folder.return_value = 'test'
|
mocked_get_thumbnail_folder.return_value = Path('test')
|
||||||
mocked_exists.return_value = True
|
|
||||||
mocked_open.side_effect = IOError()
|
|
||||||
|
|
||||||
# WHEN: calling get_titles_and_notes
|
# WHEN: calling get_titles_and_notes
|
||||||
result_titles, result_notes = self.document.get_titles_and_notes()
|
result_titles, result_notes = self.document.get_titles_and_notes()
|
||||||
|
@ -180,18 +166,16 @@ class TestPresentationDocument(TestCase):
|
||||||
patch('openlp.plugins.presentations.lib.presentationcontroller.check_directory_exists')
|
patch('openlp.plugins.presentations.lib.presentationcontroller.check_directory_exists')
|
||||||
self.get_thumbnail_folder_patcher = \
|
self.get_thumbnail_folder_patcher = \
|
||||||
patch('openlp.plugins.presentations.lib.presentationcontroller.PresentationDocument.get_thumbnail_folder')
|
patch('openlp.plugins.presentations.lib.presentationcontroller.PresentationDocument.get_thumbnail_folder')
|
||||||
self.os_patcher = patch('openlp.plugins.presentations.lib.presentationcontroller.os')
|
|
||||||
self._setup_patcher = \
|
self._setup_patcher = \
|
||||||
patch('openlp.plugins.presentations.lib.presentationcontroller.PresentationDocument._setup')
|
patch('openlp.plugins.presentations.lib.presentationcontroller.PresentationDocument._setup')
|
||||||
|
|
||||||
self.mock_check_directory_exists = self.check_directory_exists_patcher.start()
|
self.mock_check_directory_exists = self.check_directory_exists_patcher.start()
|
||||||
self.mock_get_thumbnail_folder = self.get_thumbnail_folder_patcher.start()
|
self.mock_get_thumbnail_folder = self.get_thumbnail_folder_patcher.start()
|
||||||
self.mock_os = self.os_patcher.start()
|
|
||||||
self.mock_setup = self._setup_patcher.start()
|
self.mock_setup = self._setup_patcher.start()
|
||||||
|
|
||||||
self.mock_controller = MagicMock()
|
self.mock_controller = MagicMock()
|
||||||
|
|
||||||
self.mock_get_thumbnail_folder.return_value = 'returned/path/'
|
self.mock_get_thumbnail_folder.return_value = Path('returned/path/')
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
"""
|
"""
|
||||||
|
@ -199,7 +183,6 @@ class TestPresentationDocument(TestCase):
|
||||||
"""
|
"""
|
||||||
self.check_directory_exists_patcher.stop()
|
self.check_directory_exists_patcher.stop()
|
||||||
self.get_thumbnail_folder_patcher.stop()
|
self.get_thumbnail_folder_patcher.stop()
|
||||||
self.os_patcher.stop()
|
|
||||||
self._setup_patcher.stop()
|
self._setup_patcher.stop()
|
||||||
|
|
||||||
def test_initialise_presentation_document(self):
|
def test_initialise_presentation_document(self):
|
||||||
|
@ -227,7 +210,7 @@ class TestPresentationDocument(TestCase):
|
||||||
PresentationDocument(self.mock_controller, 'Name')
|
PresentationDocument(self.mock_controller, 'Name')
|
||||||
|
|
||||||
# THEN: check_directory_exists should have been called with 'returned/path/'
|
# THEN: check_directory_exists should have been called with 'returned/path/'
|
||||||
self.mock_check_directory_exists.assert_called_once_with(Path('returned', 'path'))
|
self.mock_check_directory_exists.assert_called_once_with(Path('returned', 'path/'))
|
||||||
|
|
||||||
self._setup_patcher.start()
|
self._setup_patcher.start()
|
||||||
|
|
||||||
|
@ -244,20 +227,3 @@ class TestPresentationDocument(TestCase):
|
||||||
|
|
||||||
# THEN: load_presentation should return false
|
# THEN: load_presentation should return false
|
||||||
self.assertFalse(result, "PresentationDocument.load_presentation should return false.")
|
self.assertFalse(result, "PresentationDocument.load_presentation should return false.")
|
||||||
|
|
||||||
def test_get_file_name(self):
|
|
||||||
"""
|
|
||||||
Test the PresentationDocument.get_file_name method.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# GIVEN: A mocked os.path.split which returns a list, an instance of PresentationDocument and
|
|
||||||
# arbitary file_path.
|
|
||||||
self.mock_os.path.split.return_value = ['directory', 'file.ext']
|
|
||||||
instance = PresentationDocument(self.mock_controller, 'Name')
|
|
||||||
instance.file_path = 'filepath'
|
|
||||||
|
|
||||||
# WHEN: Calling get_file_name
|
|
||||||
result = instance.get_file_name()
|
|
||||||
|
|
||||||
# THEN: get_file_name should return 'file.ext'
|
|
||||||
self.assertEqual(result, 'file.ext')
|
|
||||||
|
|
|
@ -24,11 +24,11 @@ Package to test the openlp.core.__init__ package.
|
||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
from unittest import TestCase
|
from unittest import TestCase
|
||||||
from unittest.mock import MagicMock, patch, call
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
from PyQt5 import QtCore, QtWidgets
|
from PyQt5 import QtCore, QtWidgets
|
||||||
|
|
||||||
from openlp.core import OpenLP, parse_options
|
from openlp.core import OpenLP
|
||||||
from openlp.core.common import Settings
|
from openlp.core.common import Settings
|
||||||
|
|
||||||
from tests.helpers.testmixin import TestMixin
|
from tests.helpers.testmixin import TestMixin
|
||||||
|
@ -96,9 +96,9 @@ class TestInit(TestCase, TestMixin):
|
||||||
'build': 'bzr000'
|
'build': 'bzr000'
|
||||||
}
|
}
|
||||||
Settings().setValue('core/application version', '2.2.0')
|
Settings().setValue('core/application version', '2.2.0')
|
||||||
with patch('openlp.core.get_application_version') as mocked_get_application_version,\
|
with patch('openlp.core.get_version') as mocked_get_version,\
|
||||||
patch('openlp.core.QtWidgets.QMessageBox.question') as mocked_question:
|
patch('openlp.core.QtWidgets.QMessageBox.question') as mocked_question:
|
||||||
mocked_get_application_version.return_value = MOCKED_VERSION
|
mocked_get_version.return_value = MOCKED_VERSION
|
||||||
mocked_question.return_value = QtWidgets.QMessageBox.No
|
mocked_question.return_value = QtWidgets.QMessageBox.No
|
||||||
|
|
||||||
# WHEN: We check if a backup should be created
|
# WHEN: We check if a backup should be created
|
||||||
|
@ -122,9 +122,9 @@ class TestInit(TestCase, TestMixin):
|
||||||
Settings().setValue('core/application version', '2.0.5')
|
Settings().setValue('core/application version', '2.0.5')
|
||||||
self.openlp.splash = MagicMock()
|
self.openlp.splash = MagicMock()
|
||||||
self.openlp.splash.isVisible.return_value = True
|
self.openlp.splash.isVisible.return_value = True
|
||||||
with patch('openlp.core.get_application_version') as mocked_get_application_version,\
|
with patch('openlp.core.get_version') as mocked_get_version, \
|
||||||
patch('openlp.core.QtWidgets.QMessageBox.question') as mocked_question:
|
patch('openlp.core.QtWidgets.QMessageBox.question') as mocked_question:
|
||||||
mocked_get_application_version.return_value = MOCKED_VERSION
|
mocked_get_version.return_value = MOCKED_VERSION
|
||||||
mocked_question.return_value = QtWidgets.QMessageBox.No
|
mocked_question.return_value = QtWidgets.QMessageBox.No
|
||||||
|
|
||||||
# WHEN: We check if a backup should be created
|
# WHEN: We check if a backup should be created
|
||||||
|
|
|
@ -26,7 +26,8 @@ from unittest import TestCase
|
||||||
from unittest.mock import patch, MagicMock
|
from unittest.mock import patch, MagicMock
|
||||||
|
|
||||||
from openlp.core.common import Registry, Settings
|
from openlp.core.common import Registry, Settings
|
||||||
from openlp.core.ui import ThemeManager, ThemeForm, FileRenameForm
|
from openlp.core.common.path import Path
|
||||||
|
from openlp.core.ui import ThemeManager
|
||||||
|
|
||||||
from tests.helpers.testmixin import TestMixin
|
from tests.helpers.testmixin import TestMixin
|
||||||
|
|
||||||
|
@ -91,6 +92,23 @@ class TestThemeManager(TestCase, TestMixin):
|
||||||
assert self.theme_manager.thumb_path.startswith(self.theme_manager.path) is True, \
|
assert self.theme_manager.thumb_path.startswith(self.theme_manager.path) is True, \
|
||||||
'The thumb path and the main path should start with the same value'
|
'The thumb path and the main path should start with the same value'
|
||||||
|
|
||||||
|
def test_build_theme_path(self):
|
||||||
|
"""
|
||||||
|
Test the thememanager build_theme_path - basic test
|
||||||
|
"""
|
||||||
|
# GIVEN: A new a call to initialise
|
||||||
|
with patch('openlp.core.common.AppLocation.get_section_data_path', return_value=Path('test/path')):
|
||||||
|
Settings().setValue('themes/global theme', 'my_theme')
|
||||||
|
|
||||||
|
self.theme_manager.theme_form = MagicMock()
|
||||||
|
self.theme_manager.load_first_time_themes = MagicMock()
|
||||||
|
|
||||||
|
# WHEN: the build_theme_path is run
|
||||||
|
self.theme_manager.build_theme_path()
|
||||||
|
|
||||||
|
# THEN: The thumbnail path should be a sub path of the test path
|
||||||
|
self.assertEqual(self.theme_manager.thumb_path, Path('test/path/thumbnails'))
|
||||||
|
|
||||||
def test_click_on_new_theme(self):
|
def test_click_on_new_theme(self):
|
||||||
"""
|
"""
|
||||||
Test the on_add_theme event handler is called by the UI
|
Test the on_add_theme event handler is called by the UI
|
||||||
|
@ -109,17 +127,16 @@ class TestThemeManager(TestCase, TestMixin):
|
||||||
|
|
||||||
@patch('openlp.core.ui.themeform.ThemeForm._setup')
|
@patch('openlp.core.ui.themeform.ThemeForm._setup')
|
||||||
@patch('openlp.core.ui.filerenameform.FileRenameForm._setup')
|
@patch('openlp.core.ui.filerenameform.FileRenameForm._setup')
|
||||||
def test_bootstrap_post(self, mocked_theme_form, mocked_rename_form):
|
def test_bootstrap_post(self, mocked_rename_form, mocked_theme_form):
|
||||||
"""
|
"""
|
||||||
Test the functions of bootstrap_post_setup are called.
|
Test the functions of bootstrap_post_setup are called.
|
||||||
"""
|
"""
|
||||||
# GIVEN:
|
# GIVEN:
|
||||||
self.theme_manager.load_themes = MagicMock()
|
self.theme_manager.load_themes = MagicMock()
|
||||||
self.theme_manager.path = MagicMock()
|
self.theme_manager.theme_path = MagicMock()
|
||||||
|
|
||||||
# WHEN:
|
# WHEN:
|
||||||
self.theme_manager.bootstrap_post_set_up()
|
self.theme_manager.bootstrap_post_set_up()
|
||||||
|
|
||||||
# THEN:
|
# THEN:
|
||||||
self.assertEqual(self.theme_manager.path, self.theme_manager.theme_form.path)
|
|
||||||
self.assertEqual(1, self.theme_manager.load_themes.call_count, "load_themes should have been called once")
|
self.assertEqual(1, self.theme_manager.load_themes.call_count, "load_themes should have been called once")
|
||||||
|
|
|
@ -22,13 +22,15 @@
|
||||||
"""
|
"""
|
||||||
Package to test the openlp.plugin.bible.lib.https package.
|
Package to test the openlp.plugin.bible.lib.https package.
|
||||||
"""
|
"""
|
||||||
from unittest import TestCase, skip
|
import os
|
||||||
|
from unittest import TestCase, skipIf
|
||||||
from unittest.mock import MagicMock
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
from openlp.core.common import Registry
|
from openlp.core.common import Registry
|
||||||
from openlp.plugins.bibles.lib.importers.http import BGExtract, CWExtract, BSExtract
|
from openlp.plugins.bibles.lib.importers.http import BGExtract, CWExtract, BSExtract
|
||||||
|
|
||||||
|
|
||||||
|
@skipIf(os.environ.get('JENKINS_URL'), 'Skip Bible HTTP tests to prevent Jenkins from being blacklisted')
|
||||||
class TestBibleHTTP(TestCase):
|
class TestBibleHTTP(TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
@ -38,6 +40,7 @@ class TestBibleHTTP(TestCase):
|
||||||
Registry.create()
|
Registry.create()
|
||||||
Registry().register('service_list', MagicMock())
|
Registry().register('service_list', MagicMock())
|
||||||
Registry().register('application', MagicMock())
|
Registry().register('application', MagicMock())
|
||||||
|
Registry().register('main_window', MagicMock())
|
||||||
|
|
||||||
def test_bible_gateway_extract_books(self):
|
def test_bible_gateway_extract_books(self):
|
||||||
"""
|
"""
|
||||||
|
|
Binary file not shown.
Loading…
Reference in New Issue