diff --git a/openlp/core/lib/__init__.py b/openlp/core/lib/__init__.py index 4d8ba6bec..9390ef650 100644 --- a/openlp/core/lib/__init__.py +++ b/openlp/core/lib/__init__.py @@ -32,7 +32,7 @@ import logging import os.path import types -from PyQt4 import QtCore, QtGui +from PyQt4 import QtCore, QtGui, Qt log = logging.getLogger(__name__) @@ -318,6 +318,34 @@ def check_directory_exists(dir): except IOError: pass +def create_separated_list(stringlist): + """ + Returns a string that represents a join of a list of strings with a + localized separator. This function corresponts to + QLocale::createSeparatedList which was introduced in Qt 4.8 and implements + the algorithm from http://www.unicode.org/reports/tr35/#ListPatterns + + ``stringlist`` + List of unicode strings + """ + if Qt.qVersion() >= u'4.8': + return unicode(QtCore.QLocale.createSeparatedList(stringlist)) + if not stringlist: + return u'' + elif len(stringlist) == 1: + return stringlist[0] + elif len(stringlist) == 2: + return unicode(translate('OpenLP.core.lib', '%1 and %2', + 'Locale list separator: 2 items').arg(stringlist[0], stringlist[1])) + else: + merged = unicode(translate('OpenLP.core.lib', '%1, and %2', + u'Locale list separator: end').arg(stringlist[-2], stringlist[-1])) + for index in reversed(range(1, len(stringlist) - 2)): + merged = unicode(translate('OpenLP.core.lib', '%1, %2', + u'Locale list separator: middle').arg(stringlist[index], merged)) + return unicode(translate('OpenLP.core.lib', '%1, %2', + u'Locale list separator: start').arg(stringlist[0], merged)) + from eventreceiver import Receiver from listwidgetwithdnd import ListWidgetWithDnD from formattingtags import FormattingTags diff --git a/openlp/core/lib/db.py b/openlp/core/lib/db.py index 2e7757324..d7ca10f0f 100644 --- a/openlp/core/lib/db.py +++ b/openlp/core/lib/db.py @@ -239,6 +239,17 @@ class Manager(object): self.session.commit() self.is_dirty = True return True + except OperationalError: + # This exception clause is for users running MySQL which likes + # to terminate connections on its own without telling anyone. + # See bug #927473 + log.exception(u'Probably a MySQL issue - "MySQL has gone away"') + self.session.rollback() + self.session.add(object_instance) + if commit: + self.session.commit() + self.is_dirty = True + return True except InvalidRequestError: self.session.rollback() log.exception(u'Object save failed') @@ -260,6 +271,17 @@ class Manager(object): self.session.commit() self.is_dirty = True return True + except OperationalError: + # This exception clause is for users running MySQL which likes + # to terminate connections on its own without telling anyone. + # See bug #927473 + log.exception(u'Probably a MySQL issue, "MySQL has gone away"') + self.session.rollback() + self.session.add_all(object_list) + if commit: + self.session.commit() + self.is_dirty = True + return True except InvalidRequestError: self.session.rollback() log.exception(u'Object list save failed') @@ -278,7 +300,15 @@ class Manager(object): if not key: return object_class() else: - return self.session.query(object_class).get(key) + try: + return self.session.query(object_class).get(key) + except OperationalError: + # This exception clause is for users running MySQL which likes + # to terminate connections on its own without telling anyone. + # See bug #927473 + log.exception(u'Probably a MySQL issue, "MySQL has gone away"') + self.session.rollback() + return self.session.query(object_class).get(key) def get_object_filtered(self, object_class, filter_clause): """ @@ -290,7 +320,15 @@ class Manager(object): ``filter_clause`` The criteria to select the object by """ - return self.session.query(object_class).filter(filter_clause).first() + try: + return self.session.query(object_class).filter(filter_clause).first() + except OperationalError: + # This exception clause is for users running MySQL which likes + # to terminate connections on its own without telling anyone. + # See bug #927473 + log.exception(u'Probably a MySQL issue, "MySQL has gone away"') + self.session.rollback() + return self.session.query(object_class).filter(filter_clause).first() def get_all_objects(self, object_class, filter_clause=None, order_by_ref=None): @@ -311,10 +349,18 @@ class Manager(object): if filter_clause is not None: query = query.filter(filter_clause) if isinstance(order_by_ref, list): - return query.order_by(*order_by_ref).all() + query = query.order_by(*order_by_ref) elif order_by_ref is not None: - return query.order_by(order_by_ref).all() - return query.all() + query = query.order_by(order_by_ref) + try: + return query.all() + except OperationalError: + # This exception clause is for users running MySQL which likes + # to terminate connections on its own without telling anyone. + # See bug #927473 + log.exception(u'Probably a MySQL issue, "MySQL has gone away"') + self.session.rollback() + return query.all() def get_object_count(self, object_class, filter_clause=None): """ @@ -330,7 +376,15 @@ class Manager(object): query = self.session.query(object_class) if filter_clause is not None: query = query.filter(filter_clause) - return query.count() + try: + return query.count() + except OperationalError: + # This exception clause is for users running MySQL which likes + # to terminate connections on its own without telling anyone. + # See bug #927473 + log.exception(u'Probably a MySQL issue, "MySQL has gone away"') + self.session.rollback() + return query.count() def delete_object(self, object_class, key): """ @@ -349,6 +403,16 @@ class Manager(object): self.session.commit() self.is_dirty = True return True + except OperationalError: + # This exception clause is for users running MySQL which likes + # to terminate connections on its own without telling anyone. + # See bug #927473 + log.exception(u'Probably a MySQL issue, "MySQL has gone away"') + self.session.rollback() + self.session.delete(object_instance) + self.session.commit() + self.is_dirty = True + return True except InvalidRequestError: self.session.rollback() log.exception(u'Failed to delete object') @@ -378,6 +442,19 @@ class Manager(object): self.session.commit() self.is_dirty = True return True + except OperationalError: + # This exception clause is for users running MySQL which likes + # to terminate connections on its own without telling anyone. + # See bug #927473 + log.exception(u'Probably a MySQL issue, "MySQL has gone away"') + self.session.rollback() + query = self.session.query(object_class) + if filter_clause is not None: + query = query.filter(filter_clause) + query.delete(synchronize_session=False) + self.session.commit() + self.is_dirty = True + return True except InvalidRequestError: self.session.rollback() log.exception(u'Failed to delete %s records', object_class.__name__) diff --git a/openlp/core/ui/slidecontroller.py b/openlp/core/ui/slidecontroller.py index ade6b4bc4..23f9ffc3c 100644 --- a/openlp/core/ui/slidecontroller.py +++ b/openlp/core/ui/slidecontroller.py @@ -1254,7 +1254,7 @@ class SlideController(Controller): if wrap is None: if self.slide_limits == SlideLimits.Wrap: row = 0 - elif self.slide_limits == SlideLimits.Next: + elif self.isLive and self.slide_limits == SlideLimits.Next: self.serviceNext() return else: @@ -1281,7 +1281,7 @@ class SlideController(Controller): if row == -1: if self.slide_limits == SlideLimits.Wrap: row = self.previewListWidget.rowCount() - 1 - elif self.slide_limits == SlideLimits.Next: + elif self.isLive and self.slide_limits == SlideLimits.Next: self.keypress_queue.append(ServiceItemAction.PreviousLastSlide) self._process_queue() return diff --git a/openlp/plugins/bibles/lib/mediaitem.py b/openlp/plugins/bibles/lib/mediaitem.py index 5ec4daa50..29172a334 100644 --- a/openlp/plugins/bibles/lib/mediaitem.py +++ b/openlp/plugins/bibles/lib/mediaitem.py @@ -31,7 +31,7 @@ import locale from PyQt4 import QtCore, QtGui from openlp.core.lib import MediaManagerItem, Receiver, ItemCapabilities, \ - translate + translate, create_separated_list from openlp.core.lib.searchedit import SearchEdit from openlp.core.lib.ui import UiStrings, add_widget_completer, \ media_item_combo_box, critical_error_message_box, \ @@ -868,7 +868,7 @@ class BibleMediaItem(MediaManagerItem): service_item.add_capability(ItemCapabilities.CanLoop) service_item.add_capability(ItemCapabilities.CanWordSplit) # Service Item: Title - service_item.title = u', '.join(raw_title) + service_item.title = create_separated_list(raw_title) # Service Item: Theme if len(self.settings.bible_theme) == 0: service_item.theme = None diff --git a/openlp/plugins/songs/forms/editsongform.py b/openlp/plugins/songs/forms/editsongform.py index cfc1772ef..62a76851c 100644 --- a/openlp/plugins/songs/forms/editsongform.py +++ b/openlp/plugins/songs/forms/editsongform.py @@ -32,7 +32,8 @@ import shutil from PyQt4 import QtCore, QtGui -from openlp.core.lib import PluginStatus, Receiver, MediaType, translate +from openlp.core.lib import PluginStatus, Receiver, MediaType, translate, \ + create_separated_list from openlp.core.lib.ui import UiStrings, add_widget_completer, \ critical_error_message_box, find_and_set_in_combo_box from openlp.core.utils import AppLocation @@ -633,7 +634,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog): VerseType.translated_tag(verse[0]), verse[1:])) for count, item in enumerate(order): if item not in verses: - valid = u', '.join(verse_names) + valid = create_separated_list(verse_names) critical_error_message_box( message=unicode(translate('SongsPlugin.EditSongForm', 'The verse order is invalid. There is no verse ' diff --git a/openlp/plugins/songs/forms/songexportform.py b/openlp/plugins/songs/forms/songexportform.py index 679330ec5..2014b5d7b 100644 --- a/openlp/plugins/songs/forms/songexportform.py +++ b/openlp/plugins/songs/forms/songexportform.py @@ -33,7 +33,8 @@ import logging from PyQt4 import QtCore, QtGui -from openlp.core.lib import build_icon, Receiver, SettingsManager, translate +from openlp.core.lib import build_icon, Receiver, SettingsManager, translate, \ + create_separated_list from openlp.core.lib.ui import UiStrings, critical_error_message_box from openlp.core.ui.wizard import OpenLPWizard, WizardStrings from openlp.plugins.songs.lib.db import Song @@ -255,7 +256,7 @@ class SongExportForm(OpenLPWizard): # No need to export temporary songs. if song.temporary: continue - authors = u', '.join([author.display_name + authors = create_separated_list([author.display_name for author in song.authors]) title = u'%s (%s)' % (unicode(song.title), authors) item = QtGui.QListWidgetItem(title) diff --git a/openlp/plugins/songs/lib/mediaitem.py b/openlp/plugins/songs/lib/mediaitem.py index 1e62dd64b..ab95d794f 100644 --- a/openlp/plugins/songs/lib/mediaitem.py +++ b/openlp/plugins/songs/lib/mediaitem.py @@ -35,7 +35,7 @@ from PyQt4 import QtCore, QtGui from sqlalchemy.sql import or_ from openlp.core.lib import MediaManagerItem, Receiver, ItemCapabilities, \ - translate, check_item_selected, PluginStatus + translate, check_item_selected, PluginStatus, create_separated_list from openlp.core.lib.ui import UiStrings, context_menu_action, \ context_menu_separator from openlp.core.utils import AppLocation @@ -247,7 +247,8 @@ class SongMediaItem(MediaManagerItem): continue author_list = [author.display_name for author in song.authors] song_title = unicode(song.title) - song_detail = u'%s (%s)' % (song_title, u', '.join(author_list)) + song_detail = u'%s (%s)' % (song_title, + create_separated_list(author_list)) song_name = QtGui.QListWidgetItem(song_detail) song_name.setData(QtCore.Qt.UserRole, QtCore.QVariant(song.id)) self.listView.addItem(song_name) @@ -469,7 +470,7 @@ class SongMediaItem(MediaManagerItem): service_item.title = song.title author_list = [unicode(author.display_name) for author in song.authors] service_item.raw_footer.append(song.title) - service_item.raw_footer.append(u', '.join(author_list)) + service_item.raw_footer.append(create_separated_list(author_list)) service_item.raw_footer.append(song.copyright) if QtCore.QSettings().value(u'general/ccli number', QtCore.QVariant(u'')).toString(): diff --git a/scripts/windows-builder.py b/scripts/windows-builder.py index 43beb8988..9210bb8b2 100644 --- a/scripts/windows-builder.py +++ b/scripts/windows-builder.py @@ -107,10 +107,11 @@ Sqlalchemy Migrate import os import sys -from shutil import copy -from shutil import rmtree +from shutil import copy, rmtree from subprocess import Popen, PIPE +from ConfigParser import SafeConfigParser as ConfigParser +# Executable paths python_exe = sys.executable innosetup_exe = os.path.join(os.getenv(u'PROGRAMFILES'), 'Inno Setup 5', u'ISCC.exe') @@ -157,6 +158,10 @@ pptviewlib_path = os.path.join(source_path, u'plugins', u'presentations', u'lib', u'pptviewlib') hooks_path = os.path.join(branch_path , u'resources', u'pyinstaller') +# Transifex details -- will be read in from the file. +transifex_filename = os.path.abspath(os.path.join(branch_path, '..', + 'transifex.conf')) + def update_code(): os.chdir(branch_path) print u'Reverting any changes to the code...' @@ -264,8 +269,22 @@ def copy_windows_files(): def update_translations(): print u'Updating translations...' + if not os.path.exists(transifex_filename): + raise Exception(u'Could not find Transifex credentials file: %s' \ + % transifex_filename) + config = ConfigParser() + config.read(transifex_filename) + if not config.has_section('transifex'): + raise Exception(u'No section named "transifex" found.') + if not config.has_option('transifex', 'username'): + raise Exception(u'No option named "username" found.') + if not config.has_option('transifex', 'password'): + raise Exception(u'No option named "password" found.') + username = config.get('transifex', 'username') + password = config.get('transifex', 'password') os.chdir(script_path) - translation_utils = Popen((python_exe, i18n_utils, u'-qdpu')) + translation_utils = Popen([python_exe, i18n_utils, u'-qdpu', '-U', + username, '-P', password]) code = translation_utils.wait() if code != 0: raise Exception(u'Error running translation_utils.py')