forked from openlp/openlp
HEAD
This commit is contained in:
commit
2aff850b64
@ -73,7 +73,7 @@ class ColorButton(QtGui.QPushButton):
|
|||||||
@color.setter
|
@color.setter
|
||||||
def color(self, color):
|
def color(self, color):
|
||||||
"""
|
"""
|
||||||
Property setter to change the imstamce color
|
Property setter to change the instance color
|
||||||
|
|
||||||
:param color: String representation of a hexidecimal color
|
:param color: String representation of a hexidecimal color
|
||||||
"""
|
"""
|
||||||
|
@ -302,7 +302,7 @@ class Renderer(OpenLPMixin, RegistryMixin, RegistryProperties):
|
|||||||
lines = text.strip('\n').split('\n')
|
lines = text.strip('\n').split('\n')
|
||||||
pages.extend(self._paginate_slide(lines, line_end))
|
pages.extend(self._paginate_slide(lines, line_end))
|
||||||
break
|
break
|
||||||
count =+ 1
|
count += 1
|
||||||
else:
|
else:
|
||||||
# Clean up line endings.
|
# Clean up line endings.
|
||||||
pages = self._paginate_slide(text.split('\n'), line_end)
|
pages = self._paginate_slide(text.split('\n'), line_end)
|
||||||
|
@ -36,6 +36,7 @@ import html
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import uuid
|
import uuid
|
||||||
|
import ntpath
|
||||||
|
|
||||||
from PyQt4 import QtGui
|
from PyQt4 import QtGui
|
||||||
|
|
||||||
@ -423,8 +424,12 @@ class ServiceItem(RegistryProperties):
|
|||||||
if 'background_audio' in header:
|
if 'background_audio' in header:
|
||||||
self.background_audio = []
|
self.background_audio = []
|
||||||
for filename in header['background_audio']:
|
for filename in header['background_audio']:
|
||||||
# Give them real file paths
|
# Give them real file paths.
|
||||||
self.background_audio.append(os.path.join(path, filename))
|
filepath = filename
|
||||||
|
if path:
|
||||||
|
# Windows can handle both forward and backward slashes, so we use ntpath to get the basename
|
||||||
|
filepath = os.path.join(path, ntpath.basename(filename))
|
||||||
|
self.background_audio.append(filepath)
|
||||||
self.theme_overwritten = header.get('theme_overwritten', False)
|
self.theme_overwritten = header.get('theme_overwritten', False)
|
||||||
if self.service_item_type == ServiceItemType.Text:
|
if self.service_item_type == ServiceItemType.Text:
|
||||||
for slide in service_item['serviceitem']['data']:
|
for slide in service_item['serviceitem']['data']:
|
||||||
|
@ -225,10 +225,10 @@ class Ui_AboutDialog(object):
|
|||||||
'\n'
|
'\n'
|
||||||
'Built With\n'
|
'Built With\n'
|
||||||
' Python: http://www.python.org/\n'
|
' Python: http://www.python.org/\n'
|
||||||
' Qt4: http://qt.digia.com/\n'
|
' Qt4: http://qt.io\n'
|
||||||
' PyQt4: http://www.riverbankcomputing.co.uk/software/pyqt/'
|
' PyQt4: http://www.riverbankcomputing.co.uk/software/pyqt/intro\n'
|
||||||
'intro\n'
|
' Oxygen Icons: http://techbase.kde.org/Projects/Oxygen/\n'
|
||||||
' Oxygen Icons: http://oxygen-icons.org/\n'
|
' MuPDF: http://www.mupdf.com/\n'
|
||||||
'\n'
|
'\n'
|
||||||
'Final Credit\n'
|
'Final Credit\n'
|
||||||
' "For God so loved the world that He gave\n'
|
' "For God so loved the world that He gave\n'
|
||||||
|
@ -37,14 +37,15 @@ import urllib.request
|
|||||||
import urllib.parse
|
import urllib.parse
|
||||||
import urllib.error
|
import urllib.error
|
||||||
from tempfile import gettempdir
|
from tempfile import gettempdir
|
||||||
from configparser import ConfigParser
|
from configparser import ConfigParser, MissingSectionHeaderError, NoSectionError, NoOptionError
|
||||||
|
|
||||||
from PyQt4 import QtCore, QtGui
|
from PyQt4 import QtCore, QtGui
|
||||||
|
|
||||||
from openlp.core.common import Registry, RegistryProperties, AppLocation, Settings, check_directory_exists, \
|
from openlp.core.common import Registry, RegistryProperties, AppLocation, Settings, check_directory_exists, \
|
||||||
translate, clean_button_text
|
translate, clean_button_text, trace_error_handler
|
||||||
from openlp.core.lib import PluginStatus, build_icon
|
from openlp.core.lib import PluginStatus, build_icon
|
||||||
from openlp.core.utils import get_web_page
|
from openlp.core.lib.ui import critical_error_message_box
|
||||||
|
from openlp.core.utils import get_web_page, CONNECTION_RETRIES, CONNECTION_TIMEOUT
|
||||||
from .firsttimewizard import UiFirstTimeWizard, FirstTimePage
|
from .firsttimewizard import UiFirstTimeWizard, FirstTimePage
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
@ -89,27 +90,32 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties):
|
|||||||
super(FirstTimeForm, self).__init__(parent)
|
super(FirstTimeForm, self).__init__(parent)
|
||||||
self.setup_ui(self)
|
self.setup_ui(self)
|
||||||
|
|
||||||
|
def get_next_page_id(self):
|
||||||
|
"""
|
||||||
|
Returns the id of the next FirstTimePage to go to based on enabled plugins
|
||||||
|
"""
|
||||||
|
# The songs plugin is enabled
|
||||||
|
if FirstTimePage.Welcome < self.currentId() < FirstTimePage.Songs and self.songs_check_box.isChecked():
|
||||||
|
print('Go for songs! %r' % self.songs_check_box.isChecked())
|
||||||
|
return FirstTimePage.Songs
|
||||||
|
# The Bibles plugin is enabled
|
||||||
|
elif FirstTimePage.Welcome < self.currentId() < FirstTimePage.Bibles and self.bible_check_box.isChecked():
|
||||||
|
return FirstTimePage.Bibles
|
||||||
|
elif FirstTimePage.Welcome < self.currentId() < FirstTimePage.Themes:
|
||||||
|
return FirstTimePage.Themes
|
||||||
|
else:
|
||||||
|
return self.currentId() + 1
|
||||||
|
|
||||||
def nextId(self):
|
def nextId(self):
|
||||||
"""
|
"""
|
||||||
Determine the next page in the Wizard to go to.
|
Determine the next page in the Wizard to go to.
|
||||||
"""
|
"""
|
||||||
self.application.process_events()
|
self.application.process_events()
|
||||||
if self.currentId() == FirstTimePage.Plugins:
|
if self.currentId() == FirstTimePage.Plugins:
|
||||||
if self.has_run_wizard:
|
|
||||||
self.songs_check_box.setChecked(self.plugin_manager.get_plugin_by_name('songs').is_active())
|
|
||||||
self.bible_check_box.setChecked(self.plugin_manager.get_plugin_by_name('bibles').is_active())
|
|
||||||
self.presentation_check_box.setChecked(self.plugin_manager.get_plugin_by_name(
|
|
||||||
'presentations').is_active())
|
|
||||||
self.image_check_box.setChecked(self.plugin_manager.get_plugin_by_name('images').is_active())
|
|
||||||
self.media_check_box.setChecked(self.plugin_manager.get_plugin_by_name('media').is_active())
|
|
||||||
self.remote_check_box.setChecked(self.plugin_manager.get_plugin_by_name('remotes').is_active())
|
|
||||||
self.custom_check_box.setChecked(self.plugin_manager.get_plugin_by_name('custom').is_active())
|
|
||||||
self.song_usage_check_box.setChecked(self.plugin_manager.get_plugin_by_name('songusage').is_active())
|
|
||||||
self.alert_check_box.setChecked(self.plugin_manager.get_plugin_by_name('alerts').is_active())
|
|
||||||
if not self.web_access:
|
if not self.web_access:
|
||||||
return FirstTimePage.NoInternet
|
return FirstTimePage.NoInternet
|
||||||
else:
|
else:
|
||||||
return FirstTimePage.Songs
|
return self.get_next_page_id()
|
||||||
elif self.currentId() == FirstTimePage.Progress:
|
elif self.currentId() == FirstTimePage.Progress:
|
||||||
return -1
|
return -1
|
||||||
elif self.currentId() == FirstTimePage.NoInternet:
|
elif self.currentId() == FirstTimePage.NoInternet:
|
||||||
@ -124,7 +130,7 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties):
|
|||||||
self.application.set_normal_cursor()
|
self.application.set_normal_cursor()
|
||||||
return FirstTimePage.Defaults
|
return FirstTimePage.Defaults
|
||||||
else:
|
else:
|
||||||
return self.currentId() + 1
|
return self.get_next_page_id()
|
||||||
|
|
||||||
def exec_(self):
|
def exec_(self):
|
||||||
"""
|
"""
|
||||||
@ -141,17 +147,23 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties):
|
|||||||
"""
|
"""
|
||||||
self.screens = screens
|
self.screens = screens
|
||||||
# check to see if we have web access
|
# check to see if we have web access
|
||||||
|
self.web_access = False
|
||||||
self.web = 'http://openlp.org/files/frw/'
|
self.web = 'http://openlp.org/files/frw/'
|
||||||
self.config = ConfigParser()
|
self.config = ConfigParser()
|
||||||
user_agent = 'OpenLP/' + Registry().get('application').applicationVersion()
|
user_agent = 'OpenLP/' + Registry().get('application').applicationVersion()
|
||||||
self.web_access = get_web_page('%s%s' % (self.web, 'download.cfg'), header=('User-Agent', user_agent))
|
web_config = get_web_page('%s%s' % (self.web, 'download.cfg'), header=('User-Agent', user_agent))
|
||||||
if self.web_access:
|
if web_config:
|
||||||
files = self.web_access.read()
|
files = web_config.read()
|
||||||
self.config.read_string(files.decode())
|
try:
|
||||||
self.web = self.config.get('general', 'base url')
|
self.config.read_string(files.decode())
|
||||||
self.songs_url = self.web + self.config.get('songs', 'directory') + '/'
|
self.web = self.config.get('general', 'base url')
|
||||||
self.bibles_url = self.web + self.config.get('bibles', 'directory') + '/'
|
self.songs_url = self.web + self.config.get('songs', 'directory') + '/'
|
||||||
self.themes_url = self.web + self.config.get('themes', 'directory') + '/'
|
self.bibles_url = self.web + self.config.get('bibles', 'directory') + '/'
|
||||||
|
self.themes_url = self.web + self.config.get('themes', 'directory') + '/'
|
||||||
|
self.web_access = True
|
||||||
|
except (NoSectionError, NoOptionError, MissingSectionHeaderError):
|
||||||
|
log.debug('A problem occured while parsing the downloaded config file')
|
||||||
|
trace_error_handler(log)
|
||||||
self.update_screen_list_combo()
|
self.update_screen_list_combo()
|
||||||
self.was_download_cancelled = False
|
self.was_download_cancelled = False
|
||||||
self.theme_screenshot_thread = None
|
self.theme_screenshot_thread = None
|
||||||
@ -171,6 +183,17 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties):
|
|||||||
self.no_internet_finish_button.setVisible(False)
|
self.no_internet_finish_button.setVisible(False)
|
||||||
# Check if this is a re-run of the wizard.
|
# Check if this is a re-run of the wizard.
|
||||||
self.has_run_wizard = Settings().value('core/has run wizard')
|
self.has_run_wizard = Settings().value('core/has run wizard')
|
||||||
|
if self.has_run_wizard:
|
||||||
|
self.songs_check_box.setChecked(self.plugin_manager.get_plugin_by_name('songs').is_active())
|
||||||
|
self.bible_check_box.setChecked(self.plugin_manager.get_plugin_by_name('bibles').is_active())
|
||||||
|
self.presentation_check_box.setChecked(self.plugin_manager.get_plugin_by_name('presentations').is_active())
|
||||||
|
self.image_check_box.setChecked(self.plugin_manager.get_plugin_by_name('images').is_active())
|
||||||
|
self.media_check_box.setChecked(self.plugin_manager.get_plugin_by_name('media').is_active())
|
||||||
|
self.remote_check_box.setChecked(self.plugin_manager.get_plugin_by_name('remotes').is_active())
|
||||||
|
self.custom_check_box.setChecked(self.plugin_manager.get_plugin_by_name('custom').is_active())
|
||||||
|
self.song_usage_check_box.setChecked(self.plugin_manager.get_plugin_by_name('songusage').is_active())
|
||||||
|
self.alert_check_box.setChecked(self.plugin_manager.get_plugin_by_name('alerts').is_active())
|
||||||
|
self.application.set_normal_cursor()
|
||||||
# Sort out internet access for downloads
|
# Sort out internet access for downloads
|
||||||
if self.web_access:
|
if self.web_access:
|
||||||
songs = self.config.get('songs', 'languages')
|
songs = self.config.get('songs', 'languages')
|
||||||
@ -200,7 +223,6 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties):
|
|||||||
# Download the theme screenshots.
|
# Download the theme screenshots.
|
||||||
self.theme_screenshot_thread = ThemeScreenshotThread(self)
|
self.theme_screenshot_thread = ThemeScreenshotThread(self)
|
||||||
self.theme_screenshot_thread.start()
|
self.theme_screenshot_thread.start()
|
||||||
self.application.set_normal_cursor()
|
|
||||||
|
|
||||||
def update_screen_list_combo(self):
|
def update_screen_list_combo(self):
|
||||||
"""
|
"""
|
||||||
@ -286,24 +308,42 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties):
|
|||||||
def url_get_file(self, url, f_path):
|
def url_get_file(self, url, f_path):
|
||||||
""""
|
""""
|
||||||
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.
|
point. Returns False on download error.
|
||||||
|
|
||||||
|
:param url: URL to download
|
||||||
|
:param f_path: Destination file
|
||||||
"""
|
"""
|
||||||
block_count = 0
|
block_count = 0
|
||||||
block_size = 4096
|
block_size = 4096
|
||||||
url_file = urllib.request.urlopen(url)
|
retries = 0
|
||||||
filename = open(f_path, "wb")
|
while True:
|
||||||
# Download until finished or canceled.
|
try:
|
||||||
while not self.was_download_cancelled:
|
url_file = urllib.request.urlopen(url, timeout=CONNECTION_TIMEOUT)
|
||||||
data = url_file.read(block_size)
|
filename = open(f_path, "wb")
|
||||||
if not data:
|
# Download until finished or canceled.
|
||||||
break
|
while not self.was_download_cancelled:
|
||||||
filename.write(data)
|
data = url_file.read(block_size)
|
||||||
block_count += 1
|
if not data:
|
||||||
self._download_progress(block_count, block_size)
|
break
|
||||||
filename.close()
|
filename.write(data)
|
||||||
|
block_count += 1
|
||||||
|
self._download_progress(block_count, block_size)
|
||||||
|
filename.close()
|
||||||
|
except ConnectionError:
|
||||||
|
trace_error_handler(log)
|
||||||
|
filename.close()
|
||||||
|
os.remove(f_path)
|
||||||
|
if retries > CONNECTION_RETRIES:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
retries += 1
|
||||||
|
time.sleep(0.1)
|
||||||
|
continue
|
||||||
|
break
|
||||||
# Delete file if cancelled, it may be a partial file.
|
# Delete file if cancelled, it may be a partial file.
|
||||||
if self.was_download_cancelled:
|
if self.was_download_cancelled:
|
||||||
os.remove(f_path)
|
os.remove(f_path)
|
||||||
|
return True
|
||||||
|
|
||||||
def _build_theme_screenshots(self):
|
def _build_theme_screenshots(self):
|
||||||
"""
|
"""
|
||||||
@ -322,9 +362,19 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties):
|
|||||||
|
|
||||||
:param url: The URL of the file we want to download.
|
:param url: The URL of the file we want to download.
|
||||||
"""
|
"""
|
||||||
site = urllib.request.urlopen(url)
|
retries = 0
|
||||||
meta = site.info()
|
while True:
|
||||||
return int(meta.get("Content-Length"))
|
try:
|
||||||
|
site = urllib.request.urlopen(url, timeout=CONNECTION_TIMEOUT)
|
||||||
|
meta = site.info()
|
||||||
|
return int(meta.get("Content-Length"))
|
||||||
|
except ConnectionException:
|
||||||
|
if retries > CONNECTION_RETRIES:
|
||||||
|
raise
|
||||||
|
else:
|
||||||
|
retries += 1
|
||||||
|
time.sleep(0.1)
|
||||||
|
continue
|
||||||
|
|
||||||
def _download_progress(self, count, block_size):
|
def _download_progress(self, count, block_size):
|
||||||
"""
|
"""
|
||||||
@ -354,32 +404,41 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties):
|
|||||||
self.max_progress = 0
|
self.max_progress = 0
|
||||||
self.finish_button.setVisible(False)
|
self.finish_button.setVisible(False)
|
||||||
self.application.process_events()
|
self.application.process_events()
|
||||||
# Loop through the songs list and increase for each selected item
|
try:
|
||||||
for i in range(self.songs_list_widget.count()):
|
# Loop through the songs list and increase for each selected item
|
||||||
self.application.process_events()
|
for i in range(self.songs_list_widget.count()):
|
||||||
item = self.songs_list_widget.item(i)
|
self.application.process_events()
|
||||||
if item.checkState() == QtCore.Qt.Checked:
|
item = self.songs_list_widget.item(i)
|
||||||
filename = item.data(QtCore.Qt.UserRole)
|
if item.checkState() == QtCore.Qt.Checked:
|
||||||
size = self._get_file_size('%s%s' % (self.songs_url, filename))
|
filename = item.data(QtCore.Qt.UserRole)
|
||||||
self.max_progress += size
|
size = self._get_file_size('%s%s' % (self.songs_url, filename))
|
||||||
# Loop through the Bibles list and increase for each selected item
|
self.max_progress += size
|
||||||
iterator = QtGui.QTreeWidgetItemIterator(self.bibles_tree_widget)
|
# Loop through the Bibles list and increase for each selected item
|
||||||
while iterator.value():
|
iterator = QtGui.QTreeWidgetItemIterator(self.bibles_tree_widget)
|
||||||
self.application.process_events()
|
while iterator.value():
|
||||||
item = iterator.value()
|
self.application.process_events()
|
||||||
if item.parent() and item.checkState(0) == QtCore.Qt.Checked:
|
item = iterator.value()
|
||||||
filename = item.data(0, QtCore.Qt.UserRole)
|
if item.parent() and item.checkState(0) == QtCore.Qt.Checked:
|
||||||
size = self._get_file_size('%s%s' % (self.bibles_url, filename))
|
filename = item.data(0, QtCore.Qt.UserRole)
|
||||||
self.max_progress += size
|
size = self._get_file_size('%s%s' % (self.bibles_url, filename))
|
||||||
iterator += 1
|
self.max_progress += size
|
||||||
# Loop through the themes list and increase for each selected item
|
iterator += 1
|
||||||
for i in range(self.themes_list_widget.count()):
|
# Loop through the themes list and increase for each selected item
|
||||||
self.application.process_events()
|
for i in range(self.themes_list_widget.count()):
|
||||||
item = self.themes_list_widget.item(i)
|
self.application.process_events()
|
||||||
if item.checkState() == QtCore.Qt.Checked:
|
item = self.themes_list_widget.item(i)
|
||||||
filename = item.data(QtCore.Qt.UserRole)
|
if item.checkState() == QtCore.Qt.Checked:
|
||||||
size = self._get_file_size('%s%s' % (self.themes_url, filename))
|
filename = item.data(QtCore.Qt.UserRole)
|
||||||
self.max_progress += size
|
size = self._get_file_size('%s%s' % (self.themes_url, filename))
|
||||||
|
self.max_progress += size
|
||||||
|
except ConnectionError:
|
||||||
|
trace_error_handler(log)
|
||||||
|
critical_error_message_box(translate('OpenLP.FirstTimeWizard', 'Download Error'),
|
||||||
|
translate('OpenLP.FirstTimeWizard', 'There was a connection problem during '
|
||||||
|
'download, so further downloads will be skipped. Try to re-run the '
|
||||||
|
'First Time Wizard later.'))
|
||||||
|
self.max_progress = 0
|
||||||
|
self.web_access = None
|
||||||
if self.max_progress:
|
if self.max_progress:
|
||||||
# Add on 2 for plugins status setting plus a "finished" point.
|
# Add on 2 for plugins status setting plus a "finished" point.
|
||||||
self.max_progress += 2
|
self.max_progress += 2
|
||||||
@ -443,38 +502,11 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties):
|
|||||||
self._set_plugin_status(self.song_usage_check_box, 'songusage/status')
|
self._set_plugin_status(self.song_usage_check_box, 'songusage/status')
|
||||||
self._set_plugin_status(self.alert_check_box, 'alerts/status')
|
self._set_plugin_status(self.alert_check_box, 'alerts/status')
|
||||||
if self.web_access:
|
if self.web_access:
|
||||||
# Build directories for downloads
|
if not self._download_selected():
|
||||||
songs_destination = os.path.join(gettempdir(), 'openlp')
|
critical_error_message_box(translate('OpenLP.FirstTimeWizard', 'Download Error'),
|
||||||
bibles_destination = AppLocation.get_section_data_path('bibles')
|
translate('OpenLP.FirstTimeWizard', 'There was a connection problem while '
|
||||||
themes_destination = AppLocation.get_section_data_path('themes')
|
'downloading, so further downloads will be skipped. Try to re-run '
|
||||||
# Download songs
|
'the First Time Wizard later.'))
|
||||||
for i in range(self.songs_list_widget.count()):
|
|
||||||
item = self.songs_list_widget.item(i)
|
|
||||||
if item.checkState() == QtCore.Qt.Checked:
|
|
||||||
filename = item.data(QtCore.Qt.UserRole)
|
|
||||||
self._increment_progress_bar(self.downloading % filename, 0)
|
|
||||||
self.previous_size = 0
|
|
||||||
destination = os.path.join(songs_destination, str(filename))
|
|
||||||
self.url_get_file('%s%s' % (self.songs_url, filename), destination)
|
|
||||||
# Download Bibles
|
|
||||||
bibles_iterator = QtGui.QTreeWidgetItemIterator(
|
|
||||||
self.bibles_tree_widget)
|
|
||||||
while bibles_iterator.value():
|
|
||||||
item = bibles_iterator.value()
|
|
||||||
if item.parent() and item.checkState(0) == QtCore.Qt.Checked:
|
|
||||||
bible = item.data(0, QtCore.Qt.UserRole)
|
|
||||||
self._increment_progress_bar(self.downloading % bible, 0)
|
|
||||||
self.previous_size = 0
|
|
||||||
self.url_get_file('%s%s' % (self.bibles_url, bible), os.path.join(bibles_destination, bible))
|
|
||||||
bibles_iterator += 1
|
|
||||||
# Download themes
|
|
||||||
for i in range(self.themes_list_widget.count()):
|
|
||||||
item = self.themes_list_widget.item(i)
|
|
||||||
if item.checkState() == QtCore.Qt.Checked:
|
|
||||||
theme = item.data(QtCore.Qt.UserRole)
|
|
||||||
self._increment_progress_bar(self.downloading % theme, 0)
|
|
||||||
self.previous_size = 0
|
|
||||||
self.url_get_file('%s%s' % (self.themes_url, theme), os.path.join(themes_destination, theme))
|
|
||||||
# Set Default Display
|
# Set Default Display
|
||||||
if self.display_combo_box.currentIndex() != -1:
|
if self.display_combo_box.currentIndex() != -1:
|
||||||
Settings().setValue('core/monitor', self.display_combo_box.currentIndex())
|
Settings().setValue('core/monitor', self.display_combo_box.currentIndex())
|
||||||
@ -483,6 +515,46 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties):
|
|||||||
if self.theme_combo_box.currentIndex() != -1:
|
if self.theme_combo_box.currentIndex() != -1:
|
||||||
Settings().setValue('themes/global theme', self.theme_combo_box.currentText())
|
Settings().setValue('themes/global theme', self.theme_combo_box.currentText())
|
||||||
|
|
||||||
|
def _download_selected(self):
|
||||||
|
"""
|
||||||
|
Download selected songs, bibles and themes. Returns False on download error
|
||||||
|
"""
|
||||||
|
# Build directories for downloads
|
||||||
|
songs_destination = os.path.join(gettempdir(), 'openlp')
|
||||||
|
bibles_destination = AppLocation.get_section_data_path('bibles')
|
||||||
|
themes_destination = AppLocation.get_section_data_path('themes')
|
||||||
|
# Download songs
|
||||||
|
for i in range(self.songs_list_widget.count()):
|
||||||
|
item = self.songs_list_widget.item(i)
|
||||||
|
if item.checkState() == QtCore.Qt.Checked:
|
||||||
|
filename = item.data(QtCore.Qt.UserRole)
|
||||||
|
self._increment_progress_bar(self.downloading % filename, 0)
|
||||||
|
self.previous_size = 0
|
||||||
|
destination = os.path.join(songs_destination, str(filename))
|
||||||
|
if not self.url_get_file('%s%s' % (self.songs_url, filename), destination):
|
||||||
|
return False
|
||||||
|
# Download Bibles
|
||||||
|
bibles_iterator = QtGui.QTreeWidgetItemIterator(self.bibles_tree_widget)
|
||||||
|
while bibles_iterator.value():
|
||||||
|
item = bibles_iterator.value()
|
||||||
|
if item.parent() and item.checkState(0) == QtCore.Qt.Checked:
|
||||||
|
bible = item.data(0, QtCore.Qt.UserRole)
|
||||||
|
self._increment_progress_bar(self.downloading % bible, 0)
|
||||||
|
self.previous_size = 0
|
||||||
|
if not self.url_get_file('%s%s' % (self.bibles_url, bible), os.path.join(bibles_destination, bible)):
|
||||||
|
return False
|
||||||
|
bibles_iterator += 1
|
||||||
|
# Download themes
|
||||||
|
for i in range(self.themes_list_widget.count()):
|
||||||
|
item = self.themes_list_widget.item(i)
|
||||||
|
if item.checkState() == QtCore.Qt.Checked:
|
||||||
|
theme = item.data(QtCore.Qt.UserRole)
|
||||||
|
self._increment_progress_bar(self.downloading % theme, 0)
|
||||||
|
self.previous_size = 0
|
||||||
|
if not self.url_get_file('%s%s' % (self.themes_url, theme), os.path.join(themes_destination, theme)):
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
def _set_plugin_status(self, field, tag):
|
def _set_plugin_status(self, field, tag):
|
||||||
"""
|
"""
|
||||||
Set the status of a plugin.
|
Set the status of a plugin.
|
||||||
|
@ -706,7 +706,10 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow, RegistryProperties):
|
|||||||
self.active_plugin.toggle_status(PluginStatus.Inactive)
|
self.active_plugin.toggle_status(PluginStatus.Inactive)
|
||||||
# Set global theme and
|
# Set global theme and
|
||||||
Registry().execute('theme_update_global')
|
Registry().execute('theme_update_global')
|
||||||
|
# Load the themes from files
|
||||||
self.theme_manager_contents.load_first_time_themes()
|
self.theme_manager_contents.load_first_time_themes()
|
||||||
|
# Update the theme widget
|
||||||
|
self.theme_manager_contents.load_themes()
|
||||||
# Check if any Bibles downloaded. If there are, they will be processed.
|
# Check if any Bibles downloaded. If there are, they will be processed.
|
||||||
Registry().execute('bibles_load_list', True)
|
Registry().execute('bibles_load_list', True)
|
||||||
self.application.set_normal_cursor()
|
self.application.set_normal_cursor()
|
||||||
|
@ -747,8 +747,7 @@ class ServiceManager(OpenLPMixin, RegistryMixin, QtGui.QWidget, Ui_ServiceManage
|
|||||||
'File is not a valid service.\n The content encoding is not UTF-8.'))
|
'File is not a valid service.\n The content encoding is not UTF-8.'))
|
||||||
continue
|
continue
|
||||||
os_file = ucs_file.replace('/', os.path.sep)
|
os_file = ucs_file.replace('/', os.path.sep)
|
||||||
if not os_file.startswith('audio'):
|
os_file = os.path.basename(os_file)
|
||||||
os_file = os.path.split(os_file)[1]
|
|
||||||
self.log_debug('Extract file: %s' % os_file)
|
self.log_debug('Extract file: %s' % os_file)
|
||||||
zip_info.filename = os_file
|
zip_info.filename = os_file
|
||||||
zip_file.extract(zip_info, self.service_path)
|
zip_file.extract(zip_info, self.service_path)
|
||||||
|
@ -141,6 +141,7 @@ class SlideController(DisplayController, RegistryProperties):
|
|||||||
self.slide_list = {}
|
self.slide_list = {}
|
||||||
self.slide_count = 0
|
self.slide_count = 0
|
||||||
self.slide_image = None
|
self.slide_image = None
|
||||||
|
self.controller_width = -1
|
||||||
# Layout for holding panel
|
# Layout for holding panel
|
||||||
self.panel_layout = QtGui.QVBoxLayout(self.panel)
|
self.panel_layout = QtGui.QVBoxLayout(self.panel)
|
||||||
self.panel_layout.setSpacing(0)
|
self.panel_layout.setSpacing(0)
|
||||||
@ -331,9 +332,6 @@ class SlideController(DisplayController, RegistryProperties):
|
|||||||
self.slide_layout.setMargin(0)
|
self.slide_layout.setMargin(0)
|
||||||
self.slide_layout.setObjectName('SlideLayout')
|
self.slide_layout.setObjectName('SlideLayout')
|
||||||
self.preview_display = Display(self)
|
self.preview_display = Display(self)
|
||||||
self.preview_display.setGeometry(QtCore.QRect(0, 0, 300, 300))
|
|
||||||
self.preview_display.screen = {'size': self.preview_display.geometry()}
|
|
||||||
self.preview_display.setup()
|
|
||||||
self.slide_layout.insertWidget(0, self.preview_display)
|
self.slide_layout.insertWidget(0, self.preview_display)
|
||||||
self.preview_display.hide()
|
self.preview_display.hide()
|
||||||
# Actual preview screen
|
# Actual preview screen
|
||||||
@ -382,13 +380,11 @@ class SlideController(DisplayController, RegistryProperties):
|
|||||||
Registry().register_function('slidecontroller_live_spin_delay', self.receive_spin_delay)
|
Registry().register_function('slidecontroller_live_spin_delay', self.receive_spin_delay)
|
||||||
self.toolbar.set_widget_visible(LOOP_LIST, False)
|
self.toolbar.set_widget_visible(LOOP_LIST, False)
|
||||||
self.toolbar.set_widget_visible(WIDE_MENU, False)
|
self.toolbar.set_widget_visible(WIDE_MENU, False)
|
||||||
else:
|
|
||||||
self.preview_widget.doubleClicked.connect(self.on_preview_add_to_service)
|
|
||||||
self.toolbar.set_widget_visible(['editSong'], False)
|
|
||||||
if self.is_live:
|
|
||||||
self.set_live_hot_keys(self)
|
self.set_live_hot_keys(self)
|
||||||
self.__add_actions_to_widget(self.controller)
|
self.__add_actions_to_widget(self.controller)
|
||||||
else:
|
else:
|
||||||
|
self.preview_widget.doubleClicked.connect(self.on_preview_add_to_service)
|
||||||
|
self.toolbar.set_widget_visible(['editSong'], False)
|
||||||
self.controller.addActions([self.next_item, self.previous_item])
|
self.controller.addActions([self.next_item, self.previous_item])
|
||||||
Registry().register_function('slidecontroller_%s_stop_loop' % self.type_prefix, self.on_stop_loop)
|
Registry().register_function('slidecontroller_%s_stop_loop' % self.type_prefix, self.on_stop_loop)
|
||||||
Registry().register_function('slidecontroller_%s_change' % self.type_prefix, self.on_slide_change)
|
Registry().register_function('slidecontroller_%s_change' % self.type_prefix, self.on_slide_change)
|
||||||
@ -493,6 +489,11 @@ class SlideController(DisplayController, RegistryProperties):
|
|||||||
"""
|
"""
|
||||||
self.display.setVisible(False)
|
self.display.setVisible(False)
|
||||||
self.media_controller.media_stop(self)
|
self.media_controller.media_stop(self)
|
||||||
|
# Stop looping if active
|
||||||
|
if self.play_slides_loop.isChecked():
|
||||||
|
self.on_play_slides_loop(False)
|
||||||
|
elif self.play_slides_once.isChecked():
|
||||||
|
self.on_play_slides_once(False)
|
||||||
|
|
||||||
def toggle_display(self, action):
|
def toggle_display(self, action):
|
||||||
"""
|
"""
|
||||||
@ -599,7 +600,10 @@ class SlideController(DisplayController, RegistryProperties):
|
|||||||
self.slide_preview.setFixedSize(QtCore.QSize(max_width, max_width / self.ratio))
|
self.slide_preview.setFixedSize(QtCore.QSize(max_width, max_width / self.ratio))
|
||||||
self.preview_display.setFixedSize(QtCore.QSize(max_width, max_width / self.ratio))
|
self.preview_display.setFixedSize(QtCore.QSize(max_width, max_width / self.ratio))
|
||||||
self.preview_display.screen = {'size': self.preview_display.geometry()}
|
self.preview_display.screen = {'size': self.preview_display.geometry()}
|
||||||
self.on_controller_size_changed(self.controller.width())
|
# Only update controller layout if width has actually changed
|
||||||
|
if self.controller_width != self.controller.width():
|
||||||
|
self.controller_width = self.controller.width()
|
||||||
|
self.on_controller_size_changed(self.controller_width)
|
||||||
|
|
||||||
def on_controller_size_changed(self, width):
|
def on_controller_size_changed(self, width):
|
||||||
"""
|
"""
|
||||||
@ -618,6 +622,10 @@ class SlideController(DisplayController, RegistryProperties):
|
|||||||
elif width < used_space - HIDE_MENU_THRESHOLD and not self.hide_menu.isVisible():
|
elif width < used_space - HIDE_MENU_THRESHOLD and not self.hide_menu.isVisible():
|
||||||
self.set_blank_menu(False)
|
self.set_blank_menu(False)
|
||||||
self.toolbar.set_widget_visible(NARROW_MENU)
|
self.toolbar.set_widget_visible(NARROW_MENU)
|
||||||
|
# Fallback to the standard blank toolbar if the hide_menu is not visible.
|
||||||
|
elif not self.hide_menu.isVisible():
|
||||||
|
self.toolbar.set_widget_visible(NARROW_MENU, False)
|
||||||
|
self.set_blank_menu()
|
||||||
|
|
||||||
def set_blank_menu(self, visible=True):
|
def set_blank_menu(self, visible=True):
|
||||||
"""
|
"""
|
||||||
@ -692,7 +700,9 @@ class SlideController(DisplayController, RegistryProperties):
|
|||||||
self.mediabar.show()
|
self.mediabar.show()
|
||||||
self.previous_item.setVisible(not item.is_media())
|
self.previous_item.setVisible(not item.is_media())
|
||||||
self.next_item.setVisible(not item.is_media())
|
self.next_item.setVisible(not item.is_media())
|
||||||
# The layout of the toolbar is size dependent, so make sure it fits
|
# The layout of the toolbar is size dependent, so make sure it fits. Reset stored controller_width.
|
||||||
|
if self.is_live:
|
||||||
|
self.controller_width = -1
|
||||||
self.on_controller_size_changed(self.controller.width())
|
self.on_controller_size_changed(self.controller.width())
|
||||||
# Work-around for OS X, hide and then show the toolbar
|
# Work-around for OS X, hide and then show the toolbar
|
||||||
# See bug #791050
|
# See bug #791050
|
||||||
|
@ -67,8 +67,8 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard, RegistryProperties):
|
|||||||
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)
|
||||||
self.color_button.colorChanged.connect(self.on_color_changed)
|
self.color_button.colorChanged.connect(self.on_color_changed)
|
||||||
self.image_color_button.colorChanged.connect(self.on_image_color_changed)
|
self.image_color_button.colorChanged.connect(self.on_image_color_changed)
|
||||||
self.gradient_start_button.colorChanged.connect(self.on_gradient_start_button_changed)
|
self.gradient_start_button.colorChanged.connect(self.on_gradient_start_color_changed)
|
||||||
self.gradient_end_button.colorChanged.connect(self.on_gradient_end_button_changed)
|
self.gradient_end_button.colorChanged.connect(self.on_gradient_end_color_changed)
|
||||||
self.image_browse_button.clicked.connect(self.on_image_browse_button_clicked)
|
self.image_browse_button.clicked.connect(self.on_image_browse_button_clicked)
|
||||||
self.image_file_edit.editingFinished.connect(self.on_image_file_edit_editing_finished)
|
self.image_file_edit.editingFinished.connect(self.on_image_file_edit_editing_finished)
|
||||||
self.main_color_button.colorChanged.connect(self.on_main_color_changed)
|
self.main_color_button.colorChanged.connect(self.on_main_color_changed)
|
||||||
@ -411,13 +411,13 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard, RegistryProperties):
|
|||||||
"""
|
"""
|
||||||
self.theme.background_border_color = color
|
self.theme.background_border_color = color
|
||||||
|
|
||||||
def on_gradient_start_button_changed(self, color):
|
def on_gradient_start_color_changed(self, color):
|
||||||
"""
|
"""
|
||||||
Gradient 2 _color button pushed.
|
Gradient 2 _color button pushed.
|
||||||
"""
|
"""
|
||||||
self.theme.background_start_color = color
|
self.theme.background_start_color = color
|
||||||
|
|
||||||
def on_gradient_end_button_changed(self, color):
|
def on_gradient_end_color_changed(self, color):
|
||||||
"""
|
"""
|
||||||
Gradient 2 _color button pushed.
|
Gradient 2 _color button pushed.
|
||||||
"""
|
"""
|
||||||
|
@ -35,6 +35,7 @@ import logging
|
|||||||
import locale
|
import locale
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
import time
|
||||||
from shutil import which
|
from shutil import which
|
||||||
from subprocess import Popen, PIPE
|
from subprocess import Popen, PIPE
|
||||||
import sys
|
import sys
|
||||||
@ -92,6 +93,8 @@ USER_AGENTS = {
|
|||||||
'Mozilla/5.0 (X11; NetBSD amd64; rv:18.0) Gecko/20130120 Firefox/18.0'
|
'Mozilla/5.0 (X11; NetBSD amd64; rv:18.0) Gecko/20130120 Firefox/18.0'
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
CONNECTION_TIMEOUT = 30
|
||||||
|
CONNECTION_RETRIES = 2
|
||||||
|
|
||||||
|
|
||||||
class VersionThread(QtCore.QThread):
|
class VersionThread(QtCore.QThread):
|
||||||
@ -251,10 +254,19 @@ def check_latest_version(current_version):
|
|||||||
req = urllib.request.Request('http://www.openlp.org/files/version.txt')
|
req = urllib.request.Request('http://www.openlp.org/files/version.txt')
|
||||||
req.add_header('User-Agent', 'OpenLP/%s' % current_version['full'])
|
req.add_header('User-Agent', 'OpenLP/%s' % current_version['full'])
|
||||||
remote_version = None
|
remote_version = None
|
||||||
try:
|
retries = 0
|
||||||
remote_version = str(urllib.request.urlopen(req, None).read().decode()).strip()
|
while True:
|
||||||
except IOError:
|
try:
|
||||||
log.exception('Failed to download the latest OpenLP version file')
|
remote_version = str(urllib.request.urlopen(req, None,
|
||||||
|
timeout=CONNECTION_TIMEOUT).read().decode()).strip()
|
||||||
|
except ConnectionException:
|
||||||
|
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:
|
if remote_version:
|
||||||
version_string = remote_version
|
version_string = remote_version
|
||||||
return version_string
|
return version_string
|
||||||
@ -390,11 +402,19 @@ def get_web_page(url, header=None, update_openlp=False):
|
|||||||
req.add_header(header[0], header[1])
|
req.add_header(header[0], header[1])
|
||||||
page = None
|
page = None
|
||||||
log.debug('Downloading URL = %s' % url)
|
log.debug('Downloading URL = %s' % url)
|
||||||
try:
|
retries = 0
|
||||||
page = urllib.request.urlopen(req)
|
while True:
|
||||||
log.debug('Downloaded URL = %s' % page.geturl())
|
try:
|
||||||
except urllib.error.URLError:
|
page = urllib.request.urlopen(req, timeout=CONNECTION_TIMEOUT)
|
||||||
log.exception('The web page could not be downloaded')
|
log.debug('Downloaded URL = %s' % page.geturl())
|
||||||
|
except (urllib.error.URLError, ConnectionError):
|
||||||
|
if retries > CONNECTION_RETRIES:
|
||||||
|
log.exception('The web page could not be downloaded')
|
||||||
|
raise
|
||||||
|
else:
|
||||||
|
time.sleep(0.1)
|
||||||
|
continue
|
||||||
|
break
|
||||||
if not page:
|
if not page:
|
||||||
return None
|
return None
|
||||||
if update_openlp:
|
if update_openlp:
|
||||||
|
@ -159,7 +159,7 @@ class BiblePlugin(Plugin):
|
|||||||
self.upgrade_wizard = BibleUpgradeForm(self.main_window, self.manager, self)
|
self.upgrade_wizard = BibleUpgradeForm(self.main_window, self.manager, self)
|
||||||
# If the import was not cancelled then reload.
|
# If the import was not cancelled then reload.
|
||||||
if self.upgrade_wizard.exec_():
|
if self.upgrade_wizard.exec_():
|
||||||
self.media_item.reloadBibles()
|
self.media_item.reload_bibles()
|
||||||
|
|
||||||
def on_bible_import_click(self):
|
def on_bible_import_click(self):
|
||||||
if self.media_item:
|
if self.media_item:
|
||||||
|
@ -170,6 +170,9 @@ class BibleDB(QtCore.QObject, Manager, RegistryProperties):
|
|||||||
Returns the version name of the Bible.
|
Returns the version name of the Bible.
|
||||||
"""
|
"""
|
||||||
version_name = self.get_object(BibleMeta, 'name')
|
version_name = self.get_object(BibleMeta, 'name')
|
||||||
|
# Fallback to old way of naming
|
||||||
|
if not version_name:
|
||||||
|
version_name = self.get_object(BibleMeta, 'Version')
|
||||||
self.name = version_name.value if version_name else None
|
self.name = version_name.value if version_name else None
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
@ -969,11 +972,15 @@ class OldBibleDB(QtCore.QObject, Manager):
|
|||||||
"""
|
"""
|
||||||
Returns the version name of the Bible.
|
Returns the version name of the Bible.
|
||||||
"""
|
"""
|
||||||
|
self.name = None
|
||||||
version_name = self.run_sql('SELECT value FROM metadata WHERE key = "name"')
|
version_name = self.run_sql('SELECT value FROM metadata WHERE key = "name"')
|
||||||
if version_name:
|
if version_name:
|
||||||
self.name = version_name[0][0]
|
self.name = version_name[0][0]
|
||||||
else:
|
else:
|
||||||
self.name = None
|
# Fallback to old way of naming
|
||||||
|
version_name = self.run_sql('SELECT value FROM metadata WHERE key = "Version"')
|
||||||
|
if version_name:
|
||||||
|
self.name = version_name[0][0]
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
def get_metadata(self):
|
def get_metadata(self):
|
||||||
|
@ -31,6 +31,7 @@ import os
|
|||||||
import logging
|
import logging
|
||||||
from tempfile import NamedTemporaryFile
|
from tempfile import NamedTemporaryFile
|
||||||
import re
|
import re
|
||||||
|
from shutil import which
|
||||||
from subprocess import check_output, CalledProcessError, STDOUT
|
from subprocess import check_output, CalledProcessError, STDOUT
|
||||||
|
|
||||||
from openlp.core.utils import AppLocation
|
from openlp.core.utils import AppLocation
|
||||||
@ -144,17 +145,10 @@ class PdfController(PresentationController):
|
|||||||
else:
|
else:
|
||||||
DEVNULL = open(os.devnull, 'wb')
|
DEVNULL = open(os.devnull, 'wb')
|
||||||
# First try to find mupdf
|
# First try to find mupdf
|
||||||
try:
|
self.mudrawbin = which('mudraw')
|
||||||
self.mudrawbin = check_output(['which', 'mudraw'],
|
|
||||||
stderr=DEVNULL).decode(encoding='UTF-8').rstrip('\n')
|
|
||||||
except CalledProcessError:
|
|
||||||
self.mudrawbin = ''
|
|
||||||
# if mupdf isn't installed, fallback to ghostscript
|
# if mupdf isn't installed, fallback to ghostscript
|
||||||
if not self.mudrawbin:
|
if not self.mudrawbin:
|
||||||
try:
|
self.gsbin = which('gs')
|
||||||
self.gsbin = check_output(['which', 'gs'], stderr=DEVNULL).decode(encoding='UTF-8').rstrip('\n')
|
|
||||||
except CalledProcessError:
|
|
||||||
self.gsbin = ''
|
|
||||||
# Last option: check if mudraw is placed in OpenLP base folder
|
# Last option: check if mudraw is placed in OpenLP base folder
|
||||||
if not self.mudrawbin and not self.gsbin:
|
if not self.mudrawbin and not self.gsbin:
|
||||||
application_path = AppLocation.get_directory(AppLocation.AppDir)
|
application_path = AppLocation.get_directory(AppLocation.AppDir)
|
||||||
|
@ -67,8 +67,12 @@ window.OpenLP = {
|
|||||||
var ul = $("#service-manager > div[data-role=content] > ul[data-role=listview]");
|
var ul = $("#service-manager > div[data-role=content] > ul[data-role=listview]");
|
||||||
ul.html("");
|
ul.html("");
|
||||||
$.each(data.results.items, function (idx, value) {
|
$.each(data.results.items, function (idx, value) {
|
||||||
|
var text = value["title"];
|
||||||
|
if (value["notes"]) {
|
||||||
|
text += ' - ' + value["notes"];
|
||||||
|
}
|
||||||
var li = $("<li data-icon=\"false\">").append(
|
var li = $("<li data-icon=\"false\">").append(
|
||||||
$("<a href=\"#\">").attr("value", parseInt(idx, 10)).text(value["title"]));
|
$("<a href=\"#\">").attr("value", parseInt(idx, 10)).text(text));
|
||||||
li.attr("uuid", value["id"])
|
li.attr("uuid", value["id"])
|
||||||
li.children("a").click(OpenLP.setItem);
|
li.children("a").click(OpenLP.setItem);
|
||||||
ul.append(li);
|
ul.append(li);
|
||||||
@ -98,8 +102,8 @@ window.OpenLP = {
|
|||||||
} else {
|
} else {
|
||||||
text += slide["text"];
|
text += slide["text"];
|
||||||
}
|
}
|
||||||
if (slide["notes"]) {
|
if (slide["slide_notes"]) {
|
||||||
text += ("<div style='font-size:smaller;font-weight:normal'>" + slide["notes"] + "</div>");
|
text += ("<div style='font-size:smaller;font-weight:normal'>" + slide["slide_notes"] + "</div>");
|
||||||
}
|
}
|
||||||
text = text.replace(/\n/g, '<br />');
|
text = text.replace(/\n/g, '<br />');
|
||||||
if (slide["img"]) {
|
if (slide["img"]) {
|
||||||
|
@ -114,8 +114,8 @@ window.OpenLP = {
|
|||||||
text += "<br /><img src='" + slide["img"].replace("/thumbnails/", "/thumbnails320x240/") + "'><br />";
|
text += "<br /><img src='" + slide["img"].replace("/thumbnails/", "/thumbnails320x240/") + "'><br />";
|
||||||
}
|
}
|
||||||
// use notes if available
|
// use notes if available
|
||||||
if (slide["notes"]) {
|
if (slide["slide_notes"]) {
|
||||||
text += '<br />' + slide["notes"];
|
text += '<br />' + slide["slide_notes"];
|
||||||
}
|
}
|
||||||
text = text.replace(/\n/g, "<br />");
|
text = text.replace(/\n/g, "<br />");
|
||||||
$("#currentslide").html(text);
|
$("#currentslide").html(text);
|
||||||
|
@ -521,7 +521,7 @@ class HttpRouter(RegistryProperties):
|
|||||||
if current_item.is_capable(ItemCapabilities.HasDisplayTitle):
|
if current_item.is_capable(ItemCapabilities.HasDisplayTitle):
|
||||||
item['title'] = str(frame['display_title'])
|
item['title'] = str(frame['display_title'])
|
||||||
if current_item.is_capable(ItemCapabilities.HasNotes):
|
if current_item.is_capable(ItemCapabilities.HasNotes):
|
||||||
item['notes'] = str(frame['notes'])
|
item['slide_notes'] = str(frame['notes'])
|
||||||
if current_item.is_capable(ItemCapabilities.HasThumbnails) and \
|
if current_item.is_capable(ItemCapabilities.HasThumbnails) and \
|
||||||
Settings().value('remotes/thumbnails'):
|
Settings().value('remotes/thumbnails'):
|
||||||
# If the file is under our app directory tree send the portion after the match
|
# If the file is under our app directory tree send the portion after the match
|
||||||
@ -531,8 +531,6 @@ class HttpRouter(RegistryProperties):
|
|||||||
item['text'] = str(frame['title'])
|
item['text'] = str(frame['title'])
|
||||||
item['html'] = str(frame['title'])
|
item['html'] = str(frame['title'])
|
||||||
item['selected'] = (self.live_controller.selected_row == index)
|
item['selected'] = (self.live_controller.selected_row == index)
|
||||||
if current_item.notes:
|
|
||||||
item['notes'] = item.get('notes', '') + '\n' + current_item.notes
|
|
||||||
data.append(item)
|
data.append(item)
|
||||||
json_data = {'results': {'slides': data}}
|
json_data = {'results': {'slides': data}}
|
||||||
if current_item:
|
if current_item:
|
||||||
|
@ -217,4 +217,5 @@ class OpenLPSongImport(SongImport):
|
|||||||
self.import_wizard.increment_progress_bar(WizardStrings.ImportingType % new_song.title)
|
self.import_wizard.increment_progress_bar(WizardStrings.ImportingType % new_song.title)
|
||||||
if self.stop_import_flag:
|
if self.stop_import_flag:
|
||||||
break
|
break
|
||||||
|
self.source_session.close()
|
||||||
engine.dispose()
|
engine.dispose()
|
||||||
|
@ -395,6 +395,18 @@ class SongMediaItem(MediaManagerItem):
|
|||||||
new_song = self.open_lyrics.xml_to_song(song_xml)
|
new_song = self.open_lyrics.xml_to_song(song_xml)
|
||||||
new_song.title = '%s <%s>' % \
|
new_song.title = '%s <%s>' % \
|
||||||
(new_song.title, translate('SongsPlugin.MediaItem', 'copy', 'For song cloning'))
|
(new_song.title, translate('SongsPlugin.MediaItem', 'copy', 'For song cloning'))
|
||||||
|
# Copy audio files from the old to the new song
|
||||||
|
if len(old_song.media_files) > 0:
|
||||||
|
save_path = os.path.join(AppLocation.get_section_data_path(self.plugin.name), 'audio', str(new_song.id))
|
||||||
|
check_directory_exists(save_path)
|
||||||
|
for media_file in old_song.media_files:
|
||||||
|
new_media_file_name = os.path.join(save_path, os.path.basename(media_file.file_name))
|
||||||
|
shutil.copyfile(media_file.file_name, new_media_file_name)
|
||||||
|
new_media_file = MediaFile()
|
||||||
|
new_media_file.file_name = new_media_file_name
|
||||||
|
new_media_file.type = media_file.type
|
||||||
|
new_media_file.weight = media_file.weight
|
||||||
|
new_song.media_files.append(new_media_file)
|
||||||
self.plugin.manager.save_object(new_song)
|
self.plugin.manager.save_object(new_song)
|
||||||
self.on_song_list_load()
|
self.on_song_list_load()
|
||||||
|
|
||||||
|
@ -32,10 +32,11 @@ backend for the Songs plugin
|
|||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from sqlalchemy import Column, ForeignKey, types
|
from sqlalchemy import Table, Column, ForeignKey, types
|
||||||
from sqlalchemy.sql.expression import func, false, null, text
|
from sqlalchemy.sql.expression import func, false, null, text
|
||||||
|
|
||||||
from openlp.core.lib.db import get_upgrade_op
|
from openlp.core.lib.db import get_upgrade_op
|
||||||
|
from openlp.core.common import trace_error_handler
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
__version__ = 4
|
__version__ = 4
|
||||||
@ -57,12 +58,16 @@ def upgrade_1(session, metadata):
|
|||||||
:param metadata:
|
:param metadata:
|
||||||
"""
|
"""
|
||||||
op = get_upgrade_op(session)
|
op = get_upgrade_op(session)
|
||||||
op.drop_table('media_files_songs')
|
songs_table = Table('songs', metadata, autoload=True)
|
||||||
op.add_column('media_files', Column('song_id', types.Integer(), server_default=null()))
|
if 'media_files_songs' in [t.name for t in metadata.tables.values()]:
|
||||||
op.add_column('media_files', Column('weight', types.Integer(), server_default=text('0')))
|
op.drop_table('media_files_songs')
|
||||||
if metadata.bind.url.get_dialect().name != 'sqlite':
|
op.add_column('media_files', Column('song_id', types.Integer(), server_default=null()))
|
||||||
# SQLite doesn't support ALTER TABLE ADD CONSTRAINT
|
op.add_column('media_files', Column('weight', types.Integer(), server_default=text('0')))
|
||||||
op.create_foreign_key('fk_media_files_song_id', 'media_files', 'songs', ['song_id', 'id'])
|
if metadata.bind.url.get_dialect().name != 'sqlite':
|
||||||
|
# SQLite doesn't support ALTER TABLE ADD CONSTRAINT
|
||||||
|
op.create_foreign_key('fk_media_files_song_id', 'media_files', 'songs', ['song_id', 'id'])
|
||||||
|
else:
|
||||||
|
log.warning('Skipping upgrade_1 step of upgrading the song db')
|
||||||
|
|
||||||
|
|
||||||
def upgrade_2(session, metadata):
|
def upgrade_2(session, metadata):
|
||||||
@ -72,8 +77,12 @@ def upgrade_2(session, metadata):
|
|||||||
This upgrade adds a create_date and last_modified date to the songs table
|
This upgrade adds a create_date and last_modified date to the songs table
|
||||||
"""
|
"""
|
||||||
op = get_upgrade_op(session)
|
op = get_upgrade_op(session)
|
||||||
op.add_column('songs', Column('create_date', types.DateTime(), default=func.now()))
|
songs_table = Table('songs', metadata, autoload=True)
|
||||||
op.add_column('songs', Column('last_modified', types.DateTime(), default=func.now()))
|
if 'create_date' not in [col.name for col in songs_table.c.values()]:
|
||||||
|
op.add_column('songs', Column('create_date', types.DateTime(), default=func.now()))
|
||||||
|
op.add_column('songs', Column('last_modified', types.DateTime(), default=func.now()))
|
||||||
|
else:
|
||||||
|
log.warning('Skipping upgrade_2 step of upgrading the song db')
|
||||||
|
|
||||||
|
|
||||||
def upgrade_3(session, metadata):
|
def upgrade_3(session, metadata):
|
||||||
@ -83,10 +92,14 @@ def upgrade_3(session, metadata):
|
|||||||
This upgrade adds a temporary song flag to the songs table
|
This upgrade adds a temporary song flag to the songs table
|
||||||
"""
|
"""
|
||||||
op = get_upgrade_op(session)
|
op = get_upgrade_op(session)
|
||||||
if metadata.bind.url.get_dialect().name == 'sqlite':
|
songs_table = Table('songs', metadata, autoload=True)
|
||||||
op.add_column('songs', Column('temporary', types.Boolean(create_constraint=False), server_default=false()))
|
if 'temporary' not in [col.name for col in songs_table.c.values()]:
|
||||||
|
if metadata.bind.url.get_dialect().name == 'sqlite':
|
||||||
|
op.add_column('songs', Column('temporary', types.Boolean(create_constraint=False), server_default=false()))
|
||||||
|
else:
|
||||||
|
op.add_column('songs', Column('temporary', types.Boolean(), server_default=false()))
|
||||||
else:
|
else:
|
||||||
op.add_column('songs', Column('temporary', types.Boolean(), server_default=false()))
|
log.warning('Skipping upgrade_3 step of upgrading the song db')
|
||||||
|
|
||||||
|
|
||||||
def upgrade_4(session, metadata):
|
def upgrade_4(session, metadata):
|
||||||
@ -98,11 +111,15 @@ def upgrade_4(session, metadata):
|
|||||||
# Since SQLite doesn't support changing the primary key of a table, we need to recreate the table
|
# Since SQLite doesn't support changing the primary key of a table, we need to recreate the table
|
||||||
# and copy the old values
|
# and copy the old values
|
||||||
op = get_upgrade_op(session)
|
op = get_upgrade_op(session)
|
||||||
op.create_table('authors_songs_tmp',
|
songs_table = Table('songs', metadata)
|
||||||
Column('author_id', types.Integer(), ForeignKey('authors.id'), primary_key=True),
|
if 'author_type' not in [col.name for col in songs_table.c.values()]:
|
||||||
Column('song_id', types.Integer(), ForeignKey('songs.id'), primary_key=True),
|
op.create_table('authors_songs_tmp',
|
||||||
Column('author_type', types.String(), primary_key=True,
|
Column('author_id', types.Integer(), ForeignKey('authors.id'), primary_key=True),
|
||||||
nullable=False, server_default=text('""')))
|
Column('song_id', types.Integer(), ForeignKey('songs.id'), primary_key=True),
|
||||||
op.execute('INSERT INTO authors_songs_tmp SELECT author_id, song_id, "" FROM authors_songs')
|
Column('author_type', types.String(), primary_key=True,
|
||||||
op.drop_table('authors_songs')
|
nullable=False, server_default=text('""')))
|
||||||
op.rename_table('authors_songs_tmp', 'authors_songs')
|
op.execute('INSERT INTO authors_songs_tmp SELECT author_id, song_id, "" FROM authors_songs')
|
||||||
|
op.drop_table('authors_songs')
|
||||||
|
op.rename_table('authors_songs_tmp', 'authors_songs')
|
||||||
|
else:
|
||||||
|
log.warning('Skipping upgrade_4 step of upgrading the song db')
|
||||||
|
@ -60,7 +60,7 @@ __default_settings__ = {
|
|||||||
'songs/last search type': SongSearch.Entire,
|
'songs/last search type': SongSearch.Entire,
|
||||||
'songs/last import type': SongFormat.OpenLyrics,
|
'songs/last import type': SongFormat.OpenLyrics,
|
||||||
'songs/update service on edit': False,
|
'songs/update service on edit': False,
|
||||||
'songs/search as type': False,
|
'songs/search as type': True,
|
||||||
'songs/add song from service': True,
|
'songs/add song from service': True,
|
||||||
'songs/display songbar': True,
|
'songs/display songbar': True,
|
||||||
'songs/display songbook': False,
|
'songs/display songbook': False,
|
||||||
|
@ -1,32 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
|
||||||
|
|
||||||
###############################################################################
|
|
||||||
# OpenLP - Open Source Lyrics Projection #
|
|
||||||
# --------------------------------------------------------------------------- #
|
|
||||||
# Copyright (c) 2008-2014 Raoul Snyman #
|
|
||||||
# Portions copyright (c) 2008-2014 Tim Bentley, Gerald Britton, Jonathan #
|
|
||||||
# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, #
|
|
||||||
# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. #
|
|
||||||
# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, #
|
|
||||||
# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
|
|
||||||
# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, #
|
|
||||||
# Frode Woldsund, Martin Zibricky, Patrick Zimmermann #
|
|
||||||
# --------------------------------------------------------------------------- #
|
|
||||||
# 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 #
|
|
||||||
###############################################################################
|
|
||||||
|
|
||||||
hiddenimports = ['openlp.core.ui.media.phononplayer',
|
|
||||||
'openlp.core.ui.media.vlcplayer',
|
|
||||||
'openlp.core.ui.media.webkitplayer']
|
|
@ -1,33 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
|
||||||
|
|
||||||
###############################################################################
|
|
||||||
# OpenLP - Open Source Lyrics Projection #
|
|
||||||
# --------------------------------------------------------------------------- #
|
|
||||||
# Copyright (c) 2008-2014 Raoul Snyman #
|
|
||||||
# Portions copyright (c) 2008-2014 Tim Bentley, Gerald Britton, Jonathan #
|
|
||||||
# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, #
|
|
||||||
# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. #
|
|
||||||
# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, #
|
|
||||||
# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
|
|
||||||
# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, #
|
|
||||||
# Frode Woldsund, Martin Zibricky, Patrick Zimmermann #
|
|
||||||
# --------------------------------------------------------------------------- #
|
|
||||||
# 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 #
|
|
||||||
###############################################################################
|
|
||||||
|
|
||||||
hiddenimports = ['openlp.plugins.presentations.lib.impresscontroller',
|
|
||||||
'openlp.plugins.presentations.lib.powerpointcontroller',
|
|
||||||
'openlp.plugins.presentations.lib.pptviewcontroller',
|
|
||||||
'openlp.plugins.presentations.lib.pdfcontroller']
|
|
@ -1,38 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
|
||||||
|
|
||||||
###############################################################################
|
|
||||||
# OpenLP - Open Source Lyrics Projection #
|
|
||||||
# --------------------------------------------------------------------------- #
|
|
||||||
# Copyright (c) 2008-2014 Raoul Snyman #
|
|
||||||
# Portions copyright (c) 2008-2014 Tim Bentley, Gerald Britton, Jonathan #
|
|
||||||
# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, #
|
|
||||||
# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. #
|
|
||||||
# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, #
|
|
||||||
# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
|
|
||||||
# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, #
|
|
||||||
# Frode Woldsund, Martin Zibricky, Patrick Zimmermann #
|
|
||||||
# --------------------------------------------------------------------------- #
|
|
||||||
# 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 #
|
|
||||||
###############################################################################
|
|
||||||
|
|
||||||
hiddenimports = ['plugins.songs.songsplugin',
|
|
||||||
'plugins.bibles.bibleplugin',
|
|
||||||
'plugins.presentations.presentationplugin',
|
|
||||||
'plugins.media.mediaplugin',
|
|
||||||
'plugins.images.imageplugin',
|
|
||||||
'plugins.custom.customplugin',
|
|
||||||
'plugins.songusage.songusageplugin',
|
|
||||||
'plugins.remotes.remoteplugin',
|
|
||||||
'plugins.alerts.alertsplugin']
|
|
@ -202,5 +202,5 @@ class TestColorDialog(TestCase):
|
|||||||
widget.on_clicked()
|
widget.on_clicked()
|
||||||
|
|
||||||
# THEN: change_color should have been called and the colorChanged signal should have been emitted
|
# THEN: change_color should have been called and the colorChanged signal should have been emitted
|
||||||
self.mocked_change_color.assert_call_once_with('#ffffff')
|
self.mocked_change_color.assert_called_once_with('#ffffff')
|
||||||
self.mocked_color_changed.emit.assert_called_once_with('#ffffff')
|
self.mocked_color_changed.emit.assert_called_once_with('#ffffff')
|
||||||
|
@ -46,7 +46,7 @@ VERSE = 'The Lord said to {r}Noah{/r}: \n'\
|
|||||||
'{r}C{/r}{b}h{/b}{bl}i{/bl}{y}l{/y}{g}d{/g}{pk}'\
|
'{r}C{/r}{b}h{/b}{bl}i{/bl}{y}l{/y}{g}d{/g}{pk}'\
|
||||||
'r{/pk}{o}e{/o}{pp}n{/pp} of the Lord\n'
|
'r{/pk}{o}e{/o}{pp}n{/pp} of the Lord\n'
|
||||||
FOOTER = ['Arky Arky (Unknown)', 'Public Domain', 'CCLI 123456']
|
FOOTER = ['Arky Arky (Unknown)', 'Public Domain', 'CCLI 123456']
|
||||||
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', 'service'))
|
||||||
|
|
||||||
|
|
||||||
class TestServiceItem(TestCase):
|
class TestServiceItem(TestCase):
|
||||||
@ -271,3 +271,36 @@ class TestServiceItem(TestCase):
|
|||||||
self.assertEqual(service_item.start_time, 654.375, 'Start time should be 654.375')
|
self.assertEqual(service_item.start_time, 654.375, 'Start time should be 654.375')
|
||||||
self.assertEqual(service_item.end_time, 672.069, 'End time should be 672.069')
|
self.assertEqual(service_item.end_time, 672.069, 'End time should be 672.069')
|
||||||
self.assertEqual(service_item.media_length, 17.694, 'Media length should be 17.694')
|
self.assertEqual(service_item.media_length, 17.694, 'Media length should be 17.694')
|
||||||
|
|
||||||
|
def service_item_load_song_and_audio_from_service_test(self):
|
||||||
|
"""
|
||||||
|
Test the Service Item - adding a song slide from a saved service
|
||||||
|
"""
|
||||||
|
# GIVEN: A new service item and a mocked add icon function
|
||||||
|
service_item = ServiceItem(None)
|
||||||
|
service_item.add_icon = MagicMock()
|
||||||
|
|
||||||
|
# WHEN: We add a custom from a saved service
|
||||||
|
line = convert_file_service_item(TEST_PATH, 'serviceitem-song-linked-audio.osj')
|
||||||
|
service_item.set_from_service(line, '/test/')
|
||||||
|
|
||||||
|
# THEN: We should get back a valid service item
|
||||||
|
self.assertTrue(service_item.is_valid, 'The new service item should be valid')
|
||||||
|
assert_length(0, service_item._display_frames, 'The service item should have no display frames')
|
||||||
|
assert_length(7, service_item.capabilities, 'There should be 7 default custom item capabilities')
|
||||||
|
|
||||||
|
# WHEN: We render the frames of the service item
|
||||||
|
service_item.render(True)
|
||||||
|
|
||||||
|
# THEN: The frames should also be valid
|
||||||
|
self.assertEqual('Amazing Grace', service_item.get_display_title(), 'The title should be "Amazing Grace"')
|
||||||
|
self.assertEqual(VERSE[:-1], service_item.get_frames()[0]['text'],
|
||||||
|
'The returned text matches the input, except the last line feed')
|
||||||
|
self.assertEqual(VERSE.split('\n', 1)[0], service_item.get_rendered_frame(1),
|
||||||
|
'The first line has been returned')
|
||||||
|
self.assertEqual('Amazing Grace! how sweet the s', service_item.get_frame_title(0),
|
||||||
|
'"Amazing Grace! how sweet the s" has been returned as the title')
|
||||||
|
self.assertEqual('’Twas grace that taught my hea', service_item.get_frame_title(1),
|
||||||
|
'"’Twas grace that taught my hea" has been returned as the title')
|
||||||
|
self.assertEqual('/test/amazing_grace.mp3', service_item.background_audio[0],
|
||||||
|
'"/test/amazing_grace.mp3" should be in the background_audio list')
|
||||||
|
@ -49,6 +49,22 @@ directory = bibles
|
|||||||
directory = themes
|
directory = themes
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
FAKE_BROKEN_CONFIG = b"""
|
||||||
|
[general]
|
||||||
|
base url = http://example.com/frw/
|
||||||
|
[songs]
|
||||||
|
directory = songs
|
||||||
|
[bibles]
|
||||||
|
directory = bibles
|
||||||
|
"""
|
||||||
|
|
||||||
|
FAKE_INVALID_CONFIG = b"""
|
||||||
|
<html>
|
||||||
|
<head><title>This is not a config file</title></head>
|
||||||
|
<body>Some text</body>
|
||||||
|
</html>
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
class TestFirstTimeForm(TestCase, TestMixin):
|
class TestFirstTimeForm(TestCase, TestMixin):
|
||||||
|
|
||||||
@ -104,3 +120,33 @@ class TestFirstTimeForm(TestCase, TestMixin):
|
|||||||
self.assertEqual(expected_songs_url, first_time_form.songs_url, 'The songs URL should be correct')
|
self.assertEqual(expected_songs_url, first_time_form.songs_url, 'The songs URL should be correct')
|
||||||
self.assertEqual(expected_bibles_url, first_time_form.bibles_url, 'The bibles URL should be correct')
|
self.assertEqual(expected_bibles_url, first_time_form.bibles_url, 'The bibles URL should be correct')
|
||||||
self.assertEqual(expected_themes_url, first_time_form.themes_url, 'The themes URL should be correct')
|
self.assertEqual(expected_themes_url, first_time_form.themes_url, 'The themes URL should be correct')
|
||||||
|
|
||||||
|
def broken_config_test(self):
|
||||||
|
"""
|
||||||
|
Test if we can handle an config file with missing data
|
||||||
|
"""
|
||||||
|
# GIVEN: A mocked get_web_page, a First Time Wizard, an expected screen object, and a mocked broken config file
|
||||||
|
with patch('openlp.core.ui.firsttimeform.get_web_page') as mocked_get_web_page:
|
||||||
|
first_time_form = FirstTimeForm(None)
|
||||||
|
mocked_get_web_page.return_value.read.return_value = FAKE_BROKEN_CONFIG
|
||||||
|
|
||||||
|
# WHEN: The First Time Wizard is initialised
|
||||||
|
first_time_form.initialize(MagicMock())
|
||||||
|
|
||||||
|
# THEN: The First Time Form should not have web access
|
||||||
|
self.assertFalse(first_time_form.web_access, 'There should not be web access with a broken config file')
|
||||||
|
|
||||||
|
def invalid_config_test(self):
|
||||||
|
"""
|
||||||
|
Test if we can handle an config file in invalid format
|
||||||
|
"""
|
||||||
|
# GIVEN: A mocked get_web_page, a First Time Wizard, an expected screen object, and a mocked invalid config file
|
||||||
|
with patch('openlp.core.ui.firsttimeform.get_web_page') as mocked_get_web_page:
|
||||||
|
first_time_form = FirstTimeForm(None)
|
||||||
|
mocked_get_web_page.return_value.read.return_value = FAKE_INVALID_CONFIG
|
||||||
|
|
||||||
|
# WHEN: The First Time Wizard is initialised
|
||||||
|
first_time_form.initialize(MagicMock())
|
||||||
|
|
||||||
|
# THEN: The First Time Form should not have web access
|
||||||
|
self.assertFalse(first_time_form.web_access, 'There should not be web access with an invalid config file')
|
||||||
|
@ -225,6 +225,10 @@ class TestSlideController(TestCase):
|
|||||||
Registry().register('media_controller', mocked_media_controller)
|
Registry().register('media_controller', mocked_media_controller)
|
||||||
slide_controller = SlideController(None)
|
slide_controller = SlideController(None)
|
||||||
slide_controller.display = mocked_display
|
slide_controller.display = mocked_display
|
||||||
|
play_slides = MagicMock()
|
||||||
|
play_slides.isChecked.return_value = False
|
||||||
|
slide_controller.play_slides_loop = play_slides
|
||||||
|
slide_controller.play_slides_once = play_slides
|
||||||
|
|
||||||
# WHEN: live_escape() is called
|
# WHEN: live_escape() is called
|
||||||
slide_controller.live_escape()
|
slide_controller.live_escape()
|
||||||
|
@ -335,7 +335,7 @@ class TestUtils(TestCase):
|
|||||||
self.assertEqual(1, mocked_request_object.add_header.call_count,
|
self.assertEqual(1, mocked_request_object.add_header.call_count,
|
||||||
'There should only be 1 call to add_header')
|
'There should only be 1 call to add_header')
|
||||||
mock_get_user_agent.assert_called_with()
|
mock_get_user_agent.assert_called_with()
|
||||||
mock_urlopen.assert_called_with(mocked_request_object)
|
mock_urlopen.assert_called_with(mocked_request_object, timeout=30)
|
||||||
mocked_page_object.geturl.assert_called_with()
|
mocked_page_object.geturl.assert_called_with()
|
||||||
self.assertEqual(0, MockRegistry.call_count, 'The Registry() object should have never been called')
|
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')
|
self.assertEqual(mocked_page_object, returned_page, 'The returned page should be the mock object')
|
||||||
@ -365,7 +365,7 @@ class TestUtils(TestCase):
|
|||||||
self.assertEqual(2, mocked_request_object.add_header.call_count,
|
self.assertEqual(2, mocked_request_object.add_header.call_count,
|
||||||
'There should only be 2 calls to add_header')
|
'There should only be 2 calls to add_header')
|
||||||
mock_get_user_agent.assert_called_with()
|
mock_get_user_agent.assert_called_with()
|
||||||
mock_urlopen.assert_called_with(mocked_request_object)
|
mock_urlopen.assert_called_with(mocked_request_object, timeout=30)
|
||||||
mocked_page_object.geturl.assert_called_with()
|
mocked_page_object.geturl.assert_called_with()
|
||||||
self.assertEqual(mocked_page_object, returned_page, 'The returned page should be the mock object')
|
self.assertEqual(mocked_page_object, returned_page, 'The returned page should be the mock object')
|
||||||
|
|
||||||
@ -393,7 +393,7 @@ class TestUtils(TestCase):
|
|||||||
self.assertEqual(1, mocked_request_object.add_header.call_count,
|
self.assertEqual(1, mocked_request_object.add_header.call_count,
|
||||||
'There should only be 1 call to add_header')
|
'There should only be 1 call to add_header')
|
||||||
self.assertEqual(0, mock_get_user_agent.call_count, '_get_user_agent should not have been called')
|
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)
|
mock_urlopen.assert_called_with(mocked_request_object, timeout=30)
|
||||||
mocked_page_object.geturl.assert_called_with()
|
mocked_page_object.geturl.assert_called_with()
|
||||||
self.assertEqual(mocked_page_object, returned_page, 'The returned page should be the mock object')
|
self.assertEqual(mocked_page_object, returned_page, 'The returned page should be the mock object')
|
||||||
|
|
||||||
@ -425,7 +425,7 @@ class TestUtils(TestCase):
|
|||||||
mocked_request_object.add_header.assert_called_with('User-Agent', 'user_agent')
|
mocked_request_object.add_header.assert_called_with('User-Agent', 'user_agent')
|
||||||
self.assertEqual(1, mocked_request_object.add_header.call_count,
|
self.assertEqual(1, mocked_request_object.add_header.call_count,
|
||||||
'There should only be 1 call to add_header')
|
'There should only be 1 call to add_header')
|
||||||
mock_urlopen.assert_called_with(mocked_request_object)
|
mock_urlopen.assert_called_with(mocked_request_object, timeout=30)
|
||||||
mocked_page_object.geturl.assert_called_with()
|
mocked_page_object.geturl.assert_called_with()
|
||||||
mocked_registry_object.get.assert_called_with('application')
|
mocked_registry_object.get.assert_called_with('application')
|
||||||
mocked_application_object.process_events.assert_called_with()
|
mocked_application_object.process_events.assert_called_with()
|
||||||
|
@ -29,9 +29,15 @@
|
|||||||
"""
|
"""
|
||||||
This module contains tests for the db submodule of the Songs plugin.
|
This module contains tests for the db submodule of the Songs plugin.
|
||||||
"""
|
"""
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
from unittest import TestCase
|
from unittest import TestCase
|
||||||
|
from tempfile import mkdtemp
|
||||||
|
|
||||||
from openlp.plugins.songs.lib.db import Song, Author, AuthorType
|
from openlp.plugins.songs.lib.db import Song, Author, AuthorType
|
||||||
|
from openlp.plugins.songs.lib import upgrade
|
||||||
|
from openlp.core.lib.db import upgrade_db
|
||||||
|
from tests.utils.constants import TEST_RESOURCES_PATH
|
||||||
|
|
||||||
|
|
||||||
class TestDB(TestCase):
|
class TestDB(TestCase):
|
||||||
@ -39,6 +45,18 @@ class TestDB(TestCase):
|
|||||||
Test the functions in the :mod:`db` module.
|
Test the functions in the :mod:`db` module.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
"""
|
||||||
|
Setup for tests
|
||||||
|
"""
|
||||||
|
self.tmp_folder = mkdtemp()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
"""
|
||||||
|
Clean up after tests
|
||||||
|
"""
|
||||||
|
shutil.rmtree(self.tmp_folder)
|
||||||
|
|
||||||
def test_add_author(self):
|
def test_add_author(self):
|
||||||
"""
|
"""
|
||||||
Test adding an author to a song
|
Test adding an author to a song
|
||||||
@ -153,3 +171,37 @@ class TestDB(TestCase):
|
|||||||
|
|
||||||
# THEN: It should return the name with the type in brackets
|
# THEN: It should return the name with the type in brackets
|
||||||
self.assertEqual("John Doe (Words)", display_name)
|
self.assertEqual("John Doe (Words)", display_name)
|
||||||
|
|
||||||
|
def test_upgrade_old_song_db(self):
|
||||||
|
"""
|
||||||
|
Test that we can upgrade an old song db to the current schema
|
||||||
|
"""
|
||||||
|
# GIVEN: An old song db
|
||||||
|
old_db_path = os.path.join(TEST_RESOURCES_PATH, "songs", 'songs-1.9.7.sqlite')
|
||||||
|
old_db_tmp_path = os.path.join(self.tmp_folder, 'songs-1.9.7.sqlite')
|
||||||
|
shutil.copyfile(old_db_path, old_db_tmp_path)
|
||||||
|
db_url = 'sqlite:///' + old_db_tmp_path
|
||||||
|
|
||||||
|
# WHEN: upgrading the db
|
||||||
|
updated_to_version, latest_version = upgrade_db(db_url, upgrade)
|
||||||
|
|
||||||
|
# Then the song db should have been upgraded to the latest version
|
||||||
|
self.assertEqual(updated_to_version, latest_version,
|
||||||
|
'The song DB should have been upgrade to the latest version')
|
||||||
|
|
||||||
|
def test_upgrade_invalid_song_db(self):
|
||||||
|
"""
|
||||||
|
Test that we can upgrade an invalid song db to the current schema
|
||||||
|
"""
|
||||||
|
# GIVEN: A song db with invalid version
|
||||||
|
invalid_db_path = os.path.join(TEST_RESOURCES_PATH, "songs", 'songs-2.2-invalid.sqlite')
|
||||||
|
invalid_db_tmp_path = os.path.join(self.tmp_folder, 'songs-2.2-invalid.sqlite')
|
||||||
|
shutil.copyfile(invalid_db_path, invalid_db_tmp_path)
|
||||||
|
db_url = 'sqlite:///' + invalid_db_tmp_path
|
||||||
|
|
||||||
|
# WHEN: upgrading the db
|
||||||
|
updated_to_version, latest_version = upgrade_db(db_url, upgrade)
|
||||||
|
|
||||||
|
# Then the song db should have been upgraded to the latest version without errors
|
||||||
|
self.assertEqual(updated_to_version, latest_version,
|
||||||
|
'The song DB should have been upgrade to the latest version')
|
||||||
|
@ -0,0 +1 @@
|
|||||||
|
[{"serviceitem": {"header": {"will_auto_start": false, "title": "Amazing Grace", "audit": ["Amazing Grace", ["John Newton"], "", ""], "processor": null, "theme_overwritten": false, "start_time": 0, "auto_play_slides_loop": false, "plugin": "songs", "auto_play_slides_once": false, "from_plugin": false, "media_length": 0, "xml_version": "<?xml version='1.0' encoding='UTF-8'?>\n<song xmlns=\"http://openlyrics.info/namespace/2009/song\" version=\"0.8\" createdIn=\"OpenLP 2.1.0\" modifiedIn=\"OpenLP 2.1.0\" modifiedDate=\"2014-12-03T21:31:35\"><properties><titles><title>Amazing Grace</title></titles><authors><author>John Newton</author></authors></properties><lyrics><verse name=\"v1\"><lines>Amazing Grace! how sweet the sound<br/>That saved a wretch like me;<br/>I once was lost, but now am found,<br/>Was blind, but now I see.</lines></verse><verse name=\"v2\"><lines>\u2019Twas grace that taught my heart to fear,<br/>And grace my fears relieved;<br/>How precious did that grace appear,<br/>The hour I first believed!</lines></verse><verse name=\"v3\"><lines>Through many dangers, toils and snares<br/>I have already come;<br/>\u2019Tis grace that brought me safe thus far,<br/>And grace will lead me home.</lines></verse><verse name=\"v4\"><lines>The Lord has promised good to me,<br/>His word my hope secures;<br/>He will my shield and portion be<br/>As long as life endures.</lines></verse><verse name=\"v5\"><lines>Yes, when this heart and flesh shall fail,<br/>And mortal life shall cease,<br/>I shall possess within the veil<br/>A life of joy and peace.</lines></verse><verse name=\"v6\"><lines>When we\u2019ve been there a thousand years,<br/>Bright shining as the sun,<br/>We\u2019ve no less days to sing God\u2019s praise<br/>Than when we first begun.</lines></verse></lyrics></song>", "timed_slide_interval": 0, "data": {"title": "amazing grace@", "authors": "John Newton"}, "type": 1, "background_audio": ["/home/tgc/.local/share/openlp/songs/audio/7/amazing_grace.mp3"], "theme": null, "footer": ["Amazing Grace", "Written by: John Newton"], "name": "songs", "capabilities": [2, 1, 5, 8, 9, 13, 15], "end_time": 0, "notes": "", "search": "", "icon": ":/plugins/plugin_songs.png"}, "data": [{"title": "Amazing Grace! how sweet the s", "verseTag": "V1", "raw_slide": "Amazing Grace! how sweet the sound\nThat saved a wretch like me;\nI once was lost, but now am found,\nWas blind, but now I see."}, {"title": "\u2019Twas grace that taught my hea", "verseTag": "V2", "raw_slide": "\u2019Twas grace that taught my heart to fear,\nAnd grace my fears relieved;\nHow precious did that grace appear,\nThe hour I first believed!"}, {"title": "Through many dangers, toils an", "verseTag": "V3", "raw_slide": "Through many dangers, toils and snares\nI have already come;\n\u2019Tis grace that brought me safe thus far,\nAnd grace will lead me home."}, {"title": "The Lord has promised good to ", "verseTag": "V4", "raw_slide": "The Lord has promised good to me,\nHis word my hope secures;\nHe will my shield and portion be\nAs long as life endures."}, {"title": "Yes, when this heart and flesh", "verseTag": "V5", "raw_slide": "Yes, when this heart and flesh shall fail,\nAnd mortal life shall cease,\nI shall possess within the veil\nA life of joy and peace."}, {"title": "When we\u2019ve been there a thousa", "verseTag": "V6", "raw_slide": "When we\u2019ve been there a thousand years,\nBright shining as the sun,\nWe\u2019ve no less days to sing God\u2019s praise\nThan when we first begun."}]}}]
|
BIN
tests/resources/songs/songs-1.9.7.sqlite
Normal file
BIN
tests/resources/songs/songs-1.9.7.sqlite
Normal file
Binary file not shown.
BIN
tests/resources/songs/songs-2.2-invalid.sqlite
Normal file
BIN
tests/resources/songs/songs-2.2-invalid.sqlite
Normal file
Binary file not shown.
@ -42,7 +42,7 @@ def read_service_from_file(file_name):
|
|||||||
@param file_name: File name of an OSD file residing in the tests/resources folder.
|
@param file_name: File name of an OSD file residing in the tests/resources folder.
|
||||||
@return: The service contained in the file.
|
@return: The service contained in the file.
|
||||||
"""
|
"""
|
||||||
service_file = os.path.join(TEST_RESOURCES_PATH, file_name)
|
service_file = os.path.join(TEST_RESOURCES_PATH, 'service', file_name)
|
||||||
with open(service_file, 'r') as open_file:
|
with open(service_file, 'r') as open_file:
|
||||||
service = json.load(open_file)
|
service = json.load(open_file)
|
||||||
return service
|
return service
|
||||||
|
Loading…
Reference in New Issue
Block a user