More updates

This commit is contained in:
Tim Bentley 2014-03-06 22:05:15 +00:00
parent 3ae188c287
commit a0a2e5efbc
8 changed files with 177 additions and 187 deletions

View File

@ -107,7 +107,7 @@ class SongFormat(object):
Name of the format, e.g. ``'OpenLyrics'`` Name of the format, e.g. ``'OpenLyrics'``
``'prefix'`` ``'prefix'``
Prefix for Qt objects. Use mixedCase, e.g. ``'openLyrics'`` Prefix for Qt objects. Use mixedCase, e.g. ``'open_lyrics'``
See ``SongImportForm.add_file_select_item()`` See ``SongImportForm.add_file_select_item()``
Optional attributes for each song format: Optional attributes for each song format:
@ -185,7 +185,7 @@ class SongFormat(object):
OpenLyrics: { OpenLyrics: {
'class': OpenLyricsImport, 'class': OpenLyricsImport,
'name': 'OpenLyrics', 'name': 'OpenLyrics',
'prefix': 'openLyrics', 'prefix': 'open_lyrics',
'filter': '%s (*.xml)' % translate('SongsPlugin.ImportWizardForm', 'OpenLyrics Files'), 'filter': '%s (*.xml)' % translate('SongsPlugin.ImportWizardForm', 'OpenLyrics Files'),
'comboBoxText': translate('SongsPlugin.ImportWizardForm', 'OpenLyrics or OpenLP 2.0 Exported Song') 'comboBoxText': translate('SongsPlugin.ImportWizardForm', 'OpenLyrics or OpenLP 2.0 Exported Song')
}, },

View File

@ -97,7 +97,7 @@ class SongMediaItem(MediaManagerItem):
def add_end_header_bar(self): def add_end_header_bar(self):
self.toolbar.addSeparator() self.toolbar.addSeparator()
## Song Maintenance Button ## ## Song Maintenance Button ##
self.maintenanceAction = self.toolbar.add_toolbar_action('maintenanceAction', self.maintenance_action = self.toolbar.add_toolbar_action('maintenance_action',
icon=':/songs/song_maintenance.png', icon=':/songs/song_maintenance.png',
triggers=self.on_song_maintenance_click) triggers=self.on_song_maintenance_click)
self.add_search_to_toolbar() self.add_search_to_toolbar()
@ -105,13 +105,13 @@ class SongMediaItem(MediaManagerItem):
Registry().register_function('songs_load_list', self.on_song_list_load) Registry().register_function('songs_load_list', self.on_song_list_load)
Registry().register_function('songs_preview', self.on_preview_click) Registry().register_function('songs_preview', self.on_preview_click)
QtCore.QObject.connect(self.search_text_edit, QtCore.SIGNAL('cleared()'), self.on_clear_text_button_click) QtCore.QObject.connect(self.search_text_edit, QtCore.SIGNAL('cleared()'), self.on_clear_text_button_click)
QtCore.QObject.connect(self.search_text_edit, QtCore.SIGNAL('searchTypeChanged(int)'), QtCore.QObject.connect(
self.on_search_text_button_clicked) self.search_text_edit, QtCore.SIGNAL('searchTypeChanged(int)'), self.on_search_text_button_clicked)
def add_custom_context_actions(self): def add_custom_context_actions(self):
create_widget_action(self.list_view, separator=True) create_widget_action(self.list_view, separator=True)
create_widget_action(self.list_view, create_widget_action(
text=translate('OpenLP.MediaManagerItem', '&Clone'), icon=':/general/general_clone.png', self.list_view, text=translate('OpenLP.MediaManagerItem', '&Clone'), icon=':/general/general_clone.png',
triggers=self.on_clone_click) triggers=self.on_clone_click)
def on_focus(self): def on_focus(self):
@ -123,14 +123,14 @@ class SongMediaItem(MediaManagerItem):
""" """
log.debug('config_updated') log.debug('config_updated')
self.search_as_you_type = Settings().value(self.settings_section + '/search as type') self.search_as_you_type = Settings().value(self.settings_section + '/search as type')
self.updateServiceOnEdit = Settings().value(self.settings_section + '/update service on edit') self.update_service_on_edit = Settings().value(self.settings_section + '/update service on edit')
self.addSongFromService = Settings().value(self.settings_section + '/add song from service',) self.add_song_from_service = Settings().value(self.settings_section + '/add song from service',)
def retranslateUi(self): def retranslateUi(self):
self.search_text_label.setText('%s:' % UiStrings().Search) self.search_text_label.setText('%s:' % UiStrings().Search)
self.search_text_button.setText(UiStrings().Search) self.search_text_button.setText(UiStrings().Search)
self.maintenanceAction.setText(SongStrings.SongMaintenance) self.maintenance_action.setText(SongStrings.SongMaintenance)
self.maintenanceAction.setToolTip(translate('SongsPlugin.MediaItem', self.maintenance_action.setToolTip(translate('SongsPlugin.MediaItem',
'Maintain the lists of authors, topics and books.')) 'Maintain the lists of authors, topics and books.'))
def initialise(self): def initialise(self):
@ -139,7 +139,7 @@ class SongMediaItem(MediaManagerItem):
""" """
self.song_maintenance_form = SongMaintenanceForm(self.plugin.manager, self) self.song_maintenance_form = SongMaintenanceForm(self.plugin.manager, self)
self.edit_song_form = EditSongForm(self, self.main_window, self.plugin.manager) self.edit_song_form = EditSongForm(self, self.main_window, self.plugin.manager)
self.openLyrics = OpenLyrics(self.plugin.manager) self.open_lyrics = OpenLyrics(self.plugin.manager)
self.search_text_edit.set_search_types([ self.search_text_edit.set_search_types([
(SongSearch.Entire, ':/songs/song_search_all.png', (SongSearch.Entire, ':/songs/song_search_all.png',
translate('SongsPlugin.MediaItem', 'Entire Song'), translate('SongsPlugin.MediaItem', 'Entire Song'),
@ -154,8 +154,7 @@ class SongMediaItem(MediaManagerItem):
translate('SongsPlugin.MediaItem', 'Search Authors...')), translate('SongsPlugin.MediaItem', 'Search Authors...')),
(SongSearch.Books, ':/songs/song_book_edit.png', SongStrings.SongBooks, (SongSearch.Books, ':/songs/song_book_edit.png', SongStrings.SongBooks,
translate('SongsPlugin.MediaItem', 'Search Song Books...')), translate('SongsPlugin.MediaItem', 'Search Song Books...')),
(SongSearch.Themes, ':/slides/slide_theme.png', (SongSearch.Themes, ':/slides/slide_theme.png', UiStrings().Themes, UiStrings().SearchThemes)
UiStrings().Themes, UiStrings().SearchThemes)
]) ])
self.search_text_edit.set_current_search_type(Settings().value('%s/last search type' % self.settings_section)) self.search_text_edit.set_current_search_type(Settings().value('%s/last search type' % self.settings_section))
self.config_update() self.config_update()
@ -172,64 +171,65 @@ class SongMediaItem(MediaManagerItem):
self.display_results_song(search_results) self.display_results_song(search_results)
elif search_type == SongSearch.Titles: elif search_type == SongSearch.Titles:
log.debug('Titles Search') log.debug('Titles Search')
search_results = self.plugin.manager.get_all_objects(Song, search_string = '%' + clean_string(search_keywords) + '%'
Song.search_title.like('%' + clean_string(search_keywords) + '%')) search_results = self.plugin.manager.get_all_objects(Song, Song.search_title.like(search_string))
self.display_results_song(search_results) self.display_results_song(search_results)
elif search_type == SongSearch.Lyrics: elif search_type == SongSearch.Lyrics:
log.debug('Lyrics Search') log.debug('Lyrics Search')
search_results = self.plugin.manager.get_all_objects(Song, search_string = '%' + clean_string(search_keywords) + '%'
Song.search_lyrics.like('%' + clean_string(search_keywords) + '%')) search_results = self.plugin.manager.get_all_objects(Song, Song.search_lyrics.like(search_string))
self.display_results_song(search_results) self.display_results_song(search_results)
elif search_type == SongSearch.Authors: elif search_type == SongSearch.Authors:
log.debug('Authors Search') log.debug('Authors Search')
search_results = self.plugin.manager.get_all_objects(Author, search_string = '%' + search_keywords + '%'
Author.display_name.like('%' + search_keywords + '%'), Author.display_name.asc()) search_results = self.plugin.manager.get_all_objects(
Author, Author.display_name.like(search_string), Author.display_name.asc())
self.display_results_author(search_results) self.display_results_author(search_results)
elif search_type == SongSearch.Books: elif search_type == SongSearch.Books:
log.debug('Books Search') log.debug('Books Search')
search_results = self.plugin.manager.get_all_objects(Book, search_string = '%' + search_keywords + '%'
Book.name.like('%' + search_keywords + '%'), Book.name.asc()) search_results = self.plugin.manager.get_all_objects(Book, Book.name.like(search_string), Book.name.asc())
song_number = False song_number = False
if not search_results: if not search_results:
search_keywords = search_keywords.rpartition(' ') search_keywords = search_keywords.rpartition(' ')
search_string = '%' + search_keywords + '%'
search_results = self.plugin.manager.get_all_objects(Book, search_results = self.plugin.manager.get_all_objects(Book,
Book.name.like('%' + search_keywords[0] + '%'), Book.name.asc()) Book.name.like(search_string), Book.name.asc())
song_number = re.sub(r'[^0-9]', '', search_keywords[2]) song_number = re.sub(r'[^0-9]', '', search_keywords[2])
self.display_results_book(search_results, song_number) self.display_results_book(search_results, song_number)
elif search_type == SongSearch.Themes: elif search_type == SongSearch.Themes:
log.debug('Theme Search') log.debug('Theme Search')
search_results = self.plugin.manager.get_all_objects(Song, search_string = '%' + search_keywords + '%'
Song.theme_name.like('%' + search_keywords + '%')) search_results = self.plugin.manager.get_all_objects(Song, Song.theme_name.like(search_string))
self.display_results_song(search_results) self.display_results_song(search_results)
self.check_search_result() self.check_search_result()
def search_entire(self, search_keywords): def search_entire(self, search_keywords):
return self.plugin.manager.get_all_objects(Song, search_string = '%' + clean_string(search_keywords) + '%'
or_(Song.search_title.like('%' + clean_string(search_keywords) + '%'), return self.plugin.manager.get_all_objects(
Song.search_lyrics.like('%' + clean_string(search_keywords) + '%'), Song, or_(Song.search_title.like(search_string), Song.search_lyrics.like(search_string),
Song.comments.like('%' + search_keywords.lower() + '%'))) Song.comments.like(search_string)))
def on_song_list_load(self): def on_song_list_load(self):
""" """
Handle the exit from the edit dialog and trigger remote updates Handle the exit from the edit dialog and trigger remote updates of songs
of songs
""" """
log.debug('on_song_list_load - start') log.debug('on_song_list_load - start')
# Called to redisplay the song list screen edit from a search or from the exit of the Song edit dialog. If # Called to redisplay the song list screen edit from a search or from the exit of the Song edit dialog. If
# remote editing is active Trigger it and clean up so it will not update again. Push edits to the service # remote editing is active Trigger it and clean up so it will not update again. Push edits to the service
# manager to update items # manager to update items
if self.edit_item and self.updateServiceOnEdit and not self.remote_triggered: if self.edit_item and self.update_service_on_edit and not self.remote_triggered:
item = self.build_service_item(self.edit_item) item = self.build_service_item(self.edit_item)
self.service_manager.replace_service_item(item) self.service_manager.replace_service_item(item)
self.on_search_text_button_clicked() self.on_search_text_button_clicked()
log.debug('on_song_list_load - finished') log.debug('on_song_list_load - finished')
def display_results_song(self, searchresults): def display_results_song(self, search_results):
log.debug('display results Song') log.debug('display results Song')
self.save_auto_select_id() self.save_auto_select_id()
self.list_view.clear() self.list_view.clear()
searchresults.sort(key=lambda song: song.sort_key) search_results.sort(key=lambda song: song.sort_key)
for song in searchresults: for song in search_results:
# Do not display temporary songs # Do not display temporary songs
if song.temporary: if song.temporary:
continue continue
@ -244,10 +244,10 @@ class SongMediaItem(MediaManagerItem):
self.list_view.setCurrentItem(song_name) self.list_view.setCurrentItem(song_name)
self.auto_select_id = -1 self.auto_select_id = -1
def display_results_author(self, searchresults): def display_results_author(self, search_results):
log.debug('display results Author') log.debug('display results Author')
self.list_view.clear() self.list_view.clear()
for author in searchresults: for author in search_results:
for song in author.songs: for song in author.songs:
# Do not display temporary songs # Do not display temporary songs
if song.temporary: if song.temporary:
@ -257,12 +257,11 @@ class SongMediaItem(MediaManagerItem):
song_name.setData(QtCore.Qt.UserRole, song.id) song_name.setData(QtCore.Qt.UserRole, song.id)
self.list_view.addItem(song_name) self.list_view.addItem(song_name)
def display_results_book(self, searchresults, song_number=False): def display_results_book(self, search_results, song_number=False):
log.debug('display results Book') log.debug('display results Book')
self.list_view.clear() self.list_view.clear()
for book in searchresults: for book in search_results:
songs = sorted(book.songs, key=lambda song: songs = sorted(book.songs, key=lambda song: int(re.match(r'[0-9]+', '0' + song.song_number).group()))
int(re.match(r'[0-9]+', '0' + song.song_number).group()))
for song in songs: for song in songs:
# Do not display temporary songs # Do not display temporary songs
if song.temporary: if song.temporary:
@ -305,9 +304,9 @@ class SongMediaItem(MediaManagerItem):
Registry().execute('songs_load_list') Registry().execute('songs_load_list')
def on_export_click(self): def on_export_click(self):
if not hasattr(self, 'exportWizard'): if not hasattr(self, 'export_wizard'):
self.exportWizard = SongExportForm(self, self.plugin) self.export_wizard = SongExportForm(self, self.plugin)
self.exportWizard.exec_() self.export_wizard.exec_()
def on_new_click(self): def on_new_click(self):
log.debug('on_new_click') log.debug('on_new_click')
@ -362,8 +361,8 @@ class SongMediaItem(MediaManagerItem):
""" """
if check_item_selected(self.list_view, UiStrings().SelectDelete): if check_item_selected(self.list_view, UiStrings().SelectDelete):
items = self.list_view.selectedIndexes() items = self.list_view.selectedIndexes()
if QtGui.QMessageBox.question(self, if QtGui.QMessageBox.question(
UiStrings().ConfirmDelete, self, UiStrings().ConfirmDelete,
translate('SongsPlugin.MediaItem', 'Are you sure you want to delete the %n selected song(s)?', '', translate('SongsPlugin.MediaItem', 'Are you sure you want to delete the %n selected song(s)?', '',
QtCore.QCoreApplication.CodecForTr, len(items)), QtCore.QCoreApplication.CodecForTr, len(items)),
QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Yes | QtGui.QMessageBox.No), QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Yes | QtGui.QMessageBox.No),
@ -388,17 +387,23 @@ class SongMediaItem(MediaManagerItem):
self.edit_item = self.list_view.currentItem() self.edit_item = self.list_view.currentItem()
item_id = self.edit_item.data(QtCore.Qt.UserRole) item_id = self.edit_item.data(QtCore.Qt.UserRole)
old_song = self.plugin.manager.get_object(Song, item_id) old_song = self.plugin.manager.get_object(Song, item_id)
song_xml = self.openLyrics.song_to_xml(old_song) song_xml = self.open_lyrics.song_to_xml(old_song)
new_song = self.openLyrics.xml_to_song(song_xml) new_song = self.open_lyrics.xml_to_song(song_xml)
new_song.title = '%s <%s>' % (new_song.title, new_song.title = '%s <%s>' % \
translate('SongsPlugin.MediaItem', 'copy', 'For song cloning')) (new_song.title, translate('SongsPlugin.MediaItem', 'copy', 'For song cloning'))
self.plugin.manager.save_object(new_song) self.plugin.manager.save_object(new_song)
self.on_song_list_load() self.on_song_list_load()
def generate_slide_data(self, service_item, item=None, xmlVersion=False, def generate_slide_data(self, service_item, item=None, xml_version=False, remote=False,
remote=False, context=ServiceItemContext.Service): context=ServiceItemContext.Service):
""" """
Generate the slide data. Needs to be implemented by the plugin. Generate the slide data. Needs to be implemented by the plugin.
:param service_item: The service item to be built on
:param item: The Song item to be used
:param xml_version: The xml version (not used)
:param remote: Triggered from remote
:param context: Why is it being generated
""" """
log.debug('generate_slide_data: %s, %s, %s' % (service_item, item, self.remote_song)) log.debug('generate_slide_data: %s, %s, %s' % (service_item, item, self.remote_song))
item_id = self._get_id_of_item_to_generate(item, self.remote_song) item_id = self._get_id_of_item_to_generate(item, self.remote_song)
@ -411,7 +416,6 @@ class SongMediaItem(MediaManagerItem):
song = self.plugin.manager.get_object(Song, item_id) song = self.plugin.manager.get_object(Song, item_id)
service_item.theme = song.theme_name service_item.theme = song.theme_name
service_item.edit_id = item_id service_item.edit_id = item_id
if song.lyrics.startswith('<?xml version='):
verse_list = SongXML().get_verses(song.lyrics) verse_list = SongXML().get_verses(song.lyrics)
# no verse list or only 1 space (in error) # no verse list or only 1 space (in error)
verse_tags_translated = False verse_tags_translated = False
@ -438,8 +442,8 @@ class SongMediaItem(MediaManagerItem):
if not order: if not order:
break break
for verse in verse_list: for verse in verse_list:
if verse[0]['type'][0].lower() == order[0] and (verse[0]['label'].lower() == order[1:] or \ if verse[0]['type'][0].lower() == \
not order[1:]): order[0] and (verse[0]['label'].lower() == order[1:] or not order[1:]):
if verse_tags_translated: if verse_tags_translated:
verse_index = VerseType.from_translated_tag(verse[0]['type']) verse_index = VerseType.from_translated_tag(verse[0]['type'])
else: else:
@ -447,14 +451,10 @@ class SongMediaItem(MediaManagerItem):
verse_tag = VerseType.translated_tags[verse_index] verse_tag = VerseType.translated_tags[verse_index]
verse_def = '%s%s' % (verse_tag, verse[0]['label']) verse_def = '%s%s' % (verse_tag, verse[0]['label'])
service_item.add_from_text(verse[1], verse_def) service_item.add_from_text(verse[1], verse_def)
else:
verses = song.lyrics.split('\n\n')
for slide in verses:
service_item.add_from_text(str(slide))
service_item.title = song.title service_item.title = song.title
author_list = self.generate_footer(service_item, song) author_list = self.generate_footer(service_item, song)
service_item.data_string = {'title': song.search_title, 'authors': ', '.join(author_list)} service_item.data_string = {'title': song.search_title, 'authors': ', '.join(author_list)}
service_item.xml_version = self.openLyrics.song_to_xml(song) service_item.xml_version = self.open_lyrics.song_to_xml(song)
# Add the audio file to the service item. # Add the audio file to the service item.
if song.media_files: if song.media_files:
service_item.add_capability(ItemCapabilities.HasBackgroundAudio) service_item.add_capability(ItemCapabilities.HasBackgroundAudio)
@ -466,11 +466,8 @@ class SongMediaItem(MediaManagerItem):
Generates the song footer based on a song and adds details to a service item. Generates the song footer based on a song and adds details to a service item.
author_list is only required for initial song generation. author_list is only required for initial song generation.
``item`` :param item: The service item to be amended
The service item to be amended :param song: The song to be used to generate the footer
``song``
The song to be used to generate the footer
""" """
author_list = [str(author.display_name) for author in song.authors] author_list = [str(author.display_name) for author in song.authors]
item.audit = [ item.audit = [
@ -481,8 +478,8 @@ class SongMediaItem(MediaManagerItem):
item.raw_footer.append(create_separated_list(author_list)) item.raw_footer.append(create_separated_list(author_list))
item.raw_footer.append(song.copyright) item.raw_footer.append(song.copyright)
if Settings().value('core/ccli number'): if Settings().value('core/ccli number'):
item.raw_footer.append(translate('SongsPlugin.MediaItem', 'CCLI License: ') + item.raw_footer.append(translate('SongsPlugin.MediaItem',
Settings().value('core/ccli number')) 'CCLI License: ') + Settings().value('core/ccli number'))
return author_list return author_list
def service_load(self, item): def service_load(self, item):
@ -500,8 +497,8 @@ class SongMediaItem(MediaManagerItem):
Song.search_title == (re.compile(r'\W+', re.UNICODE).sub(' ', Song.search_title == (re.compile(r'\W+', re.UNICODE).sub(' ',
item.data_string['title'].strip()) + '@').strip().lower(), Song.search_title.asc()) item.data_string['title'].strip()) + '@').strip().lower(), Song.search_title.asc())
else: else:
search_results = self.plugin.manager.get_all_objects(Song, search_results = self.plugin.manager.get_all_objects(
Song.search_title == item.data_string['title'], Song.search_title.asc()) Song, Song.search_title == item.data_string['title'], Song.search_title.asc())
edit_id = 0 edit_id = 0
add_song = True add_song = True
if search_results: if search_results:
@ -521,16 +518,16 @@ class SongMediaItem(MediaManagerItem):
# If there's any backing tracks, copy them over. # If there's any backing tracks, copy them over.
if item.background_audio: if item.background_audio:
self._update_background_audio(song, item) self._update_background_audio(song, item)
if add_song and self.addSongFromService: if add_song and self.add_song_from_service:
song = self.openLyrics.xml_to_song(item.xml_version) song = self.open_lyrics.xml_to_song(item.xml_version)
# If there's any backing tracks, copy them over. # If there's any backing tracks, copy them over.
if item.background_audio: if item.background_audio:
self._update_background_audio(song, item) self._update_background_audio(song, item)
editId = song.id edit_id = song.id
self.on_search_text_button_clicked() self.on_search_text_button_clicked()
elif add_song and not self.addSongFromService: elif add_song and not self.add_song_from_service:
# Make sure we temporary import formatting tags. # Make sure we temporary import formatting tags.
song = self.openLyrics.xml_to_song(item.xml_version, True) song = self.open_lyrics.xml_to_song(item.xml_version, True)
# If there's any backing tracks, copy them over. # If there's any backing tracks, copy them over.
if item.background_audio: if item.background_audio:
self._update_background_audio(song, item) self._update_background_audio(song, item)
@ -540,9 +537,11 @@ class SongMediaItem(MediaManagerItem):
self.generate_footer(item, song) self.generate_footer(item, song)
return item return item
def search(self, string, showError): def search(self, string, show_error):
""" """
Search for some songs Search for some songs
:param string: The string to show
:param show_error: Is this an error?
""" """
search_results = self.search_entire(string) search_results = self.search_entire(string)
return [[song.id, song.title] for song in search_results] return [[song.id, song.title] for song in search_results]

View File

@ -37,6 +37,7 @@ from openlp.plugins.songs.lib.songimport import SongImport
VERSE_TAGS = ['V', 'C', 'B', 'O', 'P', 'I', 'E'] VERSE_TAGS = ['V', 'C', 'B', 'O', 'P', 'I', 'E']
class MediaShoutImport(SongImport): class MediaShoutImport(SongImport):
""" """
The :class:`MediaShoutImport` class provides the ability to import the The :class:`MediaShoutImport` class provides the ability to import the
@ -53,38 +54,34 @@ class MediaShoutImport(SongImport):
Receive a single file to import. Receive a single file to import.
""" """
try: try:
conn = pyodbc.connect('DRIVER={Microsoft Access Driver (*.mdb)};' conn = pyodbc.connect('DRIVER={Microsoft Access Driver (*.mdb)};DBQ=%s;PWD=6NOZ4eHK7k' %
'DBQ=%s;PWD=6NOZ4eHK7k' % self.import_source) self.import_source)
except: except:
# Unfortunately no specific exception type # Unfortunately no specific exception type
self.log_error(self.import_source, self.log_error(self.import_source, translate('SongsPlugin.MediaShoutImport',
translate('SongsPlugin.MediaShoutImport', 'Unable to open the MediaShout database.')) 'Unable to open the MediaShout database.'))
return return
cursor = conn.cursor() cursor = conn.cursor()
cursor.execute('SELECT Record, Title, Author, Copyright, ' cursor.execute('SELECT Record, Title, Author, Copyright, SongID, CCLI, Notes FROM Songs ORDER BY Title')
'SongID, CCLI, Notes FROM Songs ORDER BY Title')
songs = cursor.fetchall() songs = cursor.fetchall()
self.import_wizard.progress_bar.setMaximum(len(songs)) self.import_wizard.progress_bar.setMaximum(len(songs))
for song in songs: for song in songs:
if self.stop_import_flag: if self.stop_import_flag:
break break
cursor.execute('SELECT Type, Number, Text FROM Verses ' cursor.execute('SELECT Type, Number, Text FROM Verses WHERE Record = %s ORDER BY Type, Number'
'WHERE Record = %s ORDER BY Type, Number' % song.Record) % song.Record)
verses = cursor.fetchall() verses = cursor.fetchall()
cursor.execute('SELECT Type, Number, POrder FROM PlayOrder ' cursor.execute('SELECT Type, Number, POrder FROM PlayOrder WHERE Record = %s ORDER BY POrder' % song.Record)
'WHERE Record = %s ORDER BY POrder' % song.Record)
verse_order = cursor.fetchall() verse_order = cursor.fetchall()
cursor.execute('SELECT Name FROM Themes INNER JOIN SongThemes ' cursor.execute('SELECT Name FROM Themes INNER JOIN SongThemes ON SongThemes.ThemeId = Themes.ThemeId '
'ON SongThemes.ThemeId = Themes.ThemeId '
'WHERE SongThemes.Record = %s' % song.Record) 'WHERE SongThemes.Record = %s' % song.Record)
topics = cursor.fetchall() topics = cursor.fetchall()
cursor.execute('SELECT Name FROM Groups INNER JOIN SongGroups ' cursor.execute('SELECT Name FROM Groups INNER JOIN SongGroups ON SongGroups.GroupId = Groups.GroupId '
'ON SongGroups.GroupId = Groups.GroupId '
'WHERE SongGroups.Record = %s' % song.Record) 'WHERE SongGroups.Record = %s' % song.Record)
topics += cursor.fetchall() topics += cursor.fetchall()
self.processSong(song, verses, verse_order, topics) self.process_song(song, verses, verse_order, topics)
def processSong(self, song, verses, verse_order, topics): def process_song(self, song, verses, verse_order, topics):
""" """
Create the song, i.e. title, verse etc. Create the song, i.e. title, verse etc.
""" """

View File

@ -45,6 +45,7 @@ from .songimport import SongImport
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
class OpenLPSongImport(SongImport): class OpenLPSongImport(SongImport):
""" """
The :class:`OpenLPSongImport` class provides OpenLP with the ability to The :class:`OpenLPSongImport` class provides OpenLP with the ability to
@ -54,20 +55,17 @@ class OpenLPSongImport(SongImport):
""" """
Initialise the import. Initialise the import.
``manager`` :param manager: The song manager for the running OpenLP installation.
The song manager for the running OpenLP installation. :param kwargs: The database providing the data to import.
``source_db``
The database providing the data to import.
""" """
SongImport.__init__(self, manager, **kwargs) SongImport.__init__(self, manager, **kwargs)
self.sourceSession = None self.source_session = None
def do_import(self, progressDialog=None): def do_import(self, progress_dialog=None):
""" """
Run the import for an OpenLP version 2 song database. Run the import for an OpenLP version 2 song database.
``progressDialog`` ``progress_dialog``
The QProgressDialog used when importing songs from the FRW. The QProgressDialog used when importing songs from the FRW.
""" """
@ -77,28 +75,24 @@ class OpenLPSongImport(SongImport):
""" """
pass pass
class OldBook(BaseModel): class OldBook(BaseModel):
""" """
Book model Book model
""" """
pass pass
class OldMediaFile(BaseModel): class OldMediaFile(BaseModel):
""" """
MediaFile model MediaFile model
""" """
pass pass
class OldSong(BaseModel): class OldSong(BaseModel):
""" """
Song model Song model
""" """
pass pass
class OldTopic(BaseModel): class OldTopic(BaseModel):
""" """
Topic model Topic model
@ -107,15 +101,15 @@ class OpenLPSongImport(SongImport):
# Check the file type # Check the file type
if not self.import_source.endswith('.sqlite'): if not self.import_source.endswith('.sqlite'):
self.log_error(self.import_source, self.log_error(self.import_source, translate('SongsPlugin.OpenLPSongImport',
translate('SongsPlugin.OpenLPSongImport', 'Not a valid OpenLP 2.0 song database.')) 'Not a valid OpenLP 2.0 song database.'))
return return
self.import_source = 'sqlite:///%s' % self.import_source self.import_source = 'sqlite:///%s' % self.import_source
# Load the db file # Load the db file
engine = create_engine(self.import_source) engine = create_engine(self.import_source)
source_meta = MetaData() source_meta = MetaData()
source_meta.reflect(engine) source_meta.reflect(engine)
self.sourceSession = scoped_session(sessionmaker(bind=engine)) self.source_session = scoped_session(sessionmaker(bind=engine))
if 'media_files' in list(source_meta.tables.keys()): if 'media_files' in list(source_meta.tables.keys()):
has_media_files = True has_media_files = True
else: else:
@ -143,12 +137,11 @@ class OpenLPSongImport(SongImport):
} }
if has_media_files: if has_media_files:
if isinstance(source_media_files_songs_table, Table): if isinstance(source_media_files_songs_table, Table):
song_props['media_files'] = relation(OldMediaFile, song_props['media_files'] = relation(OldMediaFile, backref='songs',
backref='songs',
secondary=source_media_files_songs_table) secondary=source_media_files_songs_table)
else: else:
song_props['media_files'] = relation(OldMediaFile, song_props['media_files'] = \
backref='songs', relation(OldMediaFile, backref='songs',
foreign_keys=[source_media_files_table.c.song_id], foreign_keys=[source_media_files_table.c.song_id],
primaryjoin=source_songs_table.c.id == source_media_files_table.c.song_id) primaryjoin=source_songs_table.c.id == source_media_files_table.c.song_id)
try: try:
@ -168,7 +161,7 @@ class OpenLPSongImport(SongImport):
except UnmappedClassError: except UnmappedClassError:
mapper(OldTopic, source_topics_table) mapper(OldTopic, source_topics_table)
source_songs = self.sourceSession.query(OldSong).all() source_songs = self.source_session.query(OldSong).all()
if self.import_wizard: if self.import_wizard:
self.import_wizard.progress_bar.setMaximum(len(source_songs)) self.import_wizard.progress_bar.setMaximum(len(source_songs))
for song in source_songs: for song in source_songs:
@ -212,17 +205,17 @@ class OpenLPSongImport(SongImport):
if has_media_files: if has_media_files:
if song.media_files: if song.media_files:
for media_file in song.media_files: for media_file in song.media_files:
existing_media_file = self.manager.get_object_filtered(MediaFile, existing_media_file = self.manager.get_object_filtered(
MediaFile.file_name == media_file.file_name) MediaFile, MediaFile.file_name == media_file.file_name)
if existing_media_file: if existing_media_file:
new_song.media_files.append(existing_media_file) new_song.media_files.append(existing_media_file)
else: else:
new_song.media_files.append(MediaFile.populate(file_name=media_file.file_name)) new_song.media_files.append(MediaFile.populate(file_name=media_file.file_name))
clean_song(self.manager, new_song) clean_song(self.manager, new_song)
self.manager.save_object(new_song) self.manager.save_object(new_song)
if progressDialog: if progress_dialog:
progressDialog.setValue(progressDialog.value() + 1) progress_dialog.setValue(progress_dialog.value() + 1)
progressDialog.setLabelText(WizardStrings.ImportingType % new_song.title) progress_dialog.setLabelText(WizardStrings.ImportingType % new_song.title)
else: else:
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:

View File

@ -27,8 +27,8 @@
# Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Temple Place, Suite 330, Boston, MA 02111-1307 USA #
############################################################################### ###############################################################################
""" """
The :mod:`openlyricsexport` module provides the functionality for exporting The :mod:`openlyricsexport` module provides the functionality for exporting songs from the database to the OpenLyrics
songs from the database to the OpenLyrics format. format.
""" """
import logging import logging
import os import os
@ -62,7 +62,7 @@ class OpenLyricsExport(object):
Export the songs. Export the songs.
""" """
log.debug('started OpenLyricsExport') log.debug('started OpenLyricsExport')
openLyrics = OpenLyrics(self.manager) open_lyrics = OpenLyrics(self.manager)
self.parent.progress_bar.setMaximum(len(self.songs)) self.parent.progress_bar.setMaximum(len(self.songs))
for song in self.songs: for song in self.songs:
self.application.process_events() self.application.process_events()
@ -70,7 +70,7 @@ class OpenLyricsExport(object):
return False return False
self.parent.increment_progress_bar(translate('SongsPlugin.OpenLyricsExport', 'Exporting "%s"...') % self.parent.increment_progress_bar(translate('SongsPlugin.OpenLyricsExport', 'Exporting "%s"...') %
song.title) song.title)
xml = openLyrics.song_to_xml(song) xml = open_lyrics.song_to_xml(song)
tree = etree.ElementTree(etree.fromstring(xml.encode())) tree = etree.ElementTree(etree.fromstring(xml.encode()))
filename = '%s (%s)' % (song.title, ', '.join([author.display_name for author in song.authors])) filename = '%s (%s)' % (song.title, ', '.join([author.display_name for author in song.authors]))
filename = clean_filename(filename) filename = clean_filename(filename)
@ -78,8 +78,8 @@ class OpenLyricsExport(object):
filename = '%s.xml' % filename[0:250 - len(self.save_path)] filename = '%s.xml' % filename[0:250 - len(self.save_path)]
# Pass a file object, because lxml does not cope with some special # Pass a file object, because lxml does not cope with some special
# characters in the path (see lp:757673 and lp:744337). # characters in the path (see lp:757673 and lp:744337).
tree.write(open(os.path.join(self.save_path, filename), 'wb'), tree.write(open(os.path.join(self.save_path, filename), 'wb'), encoding='utf-8', xml_declaration=True,
encoding='utf-8', xml_declaration=True, pretty_print=True) pretty_print=True)
return True return True
def _get_application(self): def _get_application(self):

View File

@ -54,7 +54,7 @@ class OpenLyricsImport(SongImport):
""" """
log.debug('initialise OpenLyricsImport') log.debug('initialise OpenLyricsImport')
SongImport.__init__(self, manager, **kwargs) SongImport.__init__(self, manager, **kwargs)
self.openLyrics = OpenLyrics(self.manager) self.open_lyrics = OpenLyrics(self.manager)
def do_import(self): def do_import(self):
""" """
@ -71,11 +71,11 @@ class OpenLyricsImport(SongImport):
# special characters in the path (see lp:757673 and lp:744337). # special characters in the path (see lp:757673 and lp:744337).
parsed_file = etree.parse(open(file_path, 'r'), parser) parsed_file = etree.parse(open(file_path, 'r'), parser)
xml = etree.tostring(parsed_file).decode() xml = etree.tostring(parsed_file).decode()
self.openLyrics.xml_to_song(xml) self.open_lyrics.xml_to_song(xml)
except etree.XMLSyntaxError: except etree.XMLSyntaxError:
log.exception('XML syntax error in file %s' % file_path) log.exception('XML syntax error in file %s' % file_path)
self.log_error(file_path, SongStrings.XMLSyntaxError) self.log_error(file_path, SongStrings.XMLSyntaxError)
except OpenLyricsError as exception: except OpenLyricsError as exception:
log.exception('OpenLyricsException %d in file %s: %s' log.exception('OpenLyricsException %d in file %s: %s' %
% (exception.type, file_path, exception.log_message)) (exception.type, file_path, exception.log_message))
self.log_error(file_path, exception.display_message) self.log_error(file_path, exception.display_message)

View File

@ -115,10 +115,10 @@ class OpenSongImport(SongImport):
if self.stop_import_flag: if self.stop_import_flag:
return return
song_file = open(filename) song_file = open(filename)
self.doImportFile(song_file) self.do_import_file(song_file)
song_file.close() song_file.close()
def doImportFile(self, file): def do_import_file(self, file):
""" """
Process the OpenSong file - pass in a file-like object, not a file path. Process the OpenSong file - pass in a file-like object, not a file path.
""" """
@ -132,7 +132,7 @@ class OpenSongImport(SongImport):
root = tree.getroot() root = tree.getroot()
if root.tag != 'song': if root.tag != 'song':
self.log_error(file.name, str( self.log_error(file.name, str(
translate('SongsPlugin.OpenSongImport', ('Invalid OpenSong song file. Missing song tag.')))) translate('SongsPlugin.OpenSongImport', 'Invalid OpenSong song file. Missing song tag.')))
return return
fields = dir(root) fields = dir(root)
decode = { decode = {
@ -218,7 +218,7 @@ class OpenSongImport(SongImport):
for (verse_tag, verse_num, inst) in our_verse_order: for (verse_tag, verse_num, inst) in our_verse_order:
lines = '\n'.join(verses[verse_tag][verse_num][inst]) lines = '\n'.join(verses[verse_tag][verse_num][inst])
length = 0 length = 0
while(length < len(verse_num) and verse_num[length].isnumeric()): while length < len(verse_num) and verse_num[length].isnumeric():
length += 1 length += 1
verse_def = '%s%s' % (verse_tag, verse_num[:length]) verse_def = '%s%s' % (verse_tag, verse_num[:length])
verse_joints[verse_def] = '%s\n[---]\n%s' % (verse_joints[verse_def], lines) \ verse_joints[verse_def] = '%s\n[---]\n%s' % (verse_joints[verse_def], lines) \
@ -248,7 +248,7 @@ class OpenSongImport(SongImport):
if verse_num in verses.get(verse_tag, {}): if verse_num in verses.get(verse_tag, {}):
self.verse_order_list.append(verse_def) self.verse_order_list.append(verse_def)
else: else:
log.info('Got order %s but not in verse tags, dropping' log.info('Got order %s but not in verse tags, dropping this item from presentation order',
'this item from presentation order', verse_def) verse_def)
if not self.finish(): if not self.finish():
self.log_error(file.name) self.log_error(file.name)

View File

@ -39,6 +39,7 @@ from openlp.plugins.songs.lib.songimport import SongImport
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
class PowerSongImport(SongImport): class PowerSongImport(SongImport):
""" """
The :class:`PowerSongImport` class provides the ability to import song files The :class:`PowerSongImport` class provides the ability to import song files
@ -90,7 +91,7 @@ class PowerSongImport(SongImport):
Receive either a list of files or a folder (unicode) to import. Receive either a list of files or a folder (unicode) to import.
""" """
from .importer import SongFormat from .importer import SongFormat
PS_string = SongFormat.get(SongFormat.PowerSong, 'name') ps_string = SongFormat.get(SongFormat.PowerSong, 'name')
if isinstance(self.import_source, str): if isinstance(self.import_source, str):
if os.path.isdir(self.import_source): if os.path.isdir(self.import_source):
dir = self.import_source dir = self.import_source
@ -102,7 +103,7 @@ class PowerSongImport(SongImport):
self.import_source = '' self.import_source = ''
if not self.import_source or not isinstance(self.import_source, list): if not self.import_source or not isinstance(self.import_source, list):
self.log_error(translate('SongsPlugin.PowerSongImport', 'No songs to import.'), self.log_error(translate('SongsPlugin.PowerSongImport', 'No songs to import.'),
translate('SongsPlugin.PowerSongImport', 'No %s files found.') % PS_string) translate('SongsPlugin.PowerSongImport', 'No %s files found.') % ps_string)
return return
self.import_wizard.progress_bar.setMaximum(len(self.import_source)) self.import_wizard.progress_bar.setMaximum(len(self.import_source))
for file in self.import_source: for file in self.import_source:
@ -113,15 +114,15 @@ class PowerSongImport(SongImport):
with open(file, 'rb') as song_data: with open(file, 'rb') as song_data:
while True: while True:
try: try:
label = self._readString(song_data) label = self._read_string(song_data)
if not label: if not label:
break break
field = self._readString(song_data) field = self._read_string(song_data)
except ValueError: except ValueError:
parse_error = True parse_error = True
self.log_error(os.path.basename(file), str( self.log_error(os.path.basename(file), str(
translate('SongsPlugin.PowerSongImport', 'Invalid %s file. Unexpected byte value.')) % translate('SongsPlugin.PowerSongImport', 'Invalid %s file. Unexpected byte value.')) %
PS_string) ps_string)
break break
else: else:
if label == 'TITLE': if label == 'TITLE':
@ -130,7 +131,7 @@ class PowerSongImport(SongImport):
self.parse_author(field) self.parse_author(field)
elif label == 'COPYRIGHTLINE': elif label == 'COPYRIGHTLINE':
found_copyright = True found_copyright = True
self._parseCopyrightCCLI(field) self._parse_copyright_cCCLI(field)
elif label == 'PART': elif label == 'PART':
self.add_verse(field) self.add_verse(field)
if parse_error: if parse_error:
@ -138,13 +139,13 @@ class PowerSongImport(SongImport):
# Check that file had TITLE field # Check that file had TITLE field
if not self.title: if not self.title:
self.log_error(os.path.basename(file), str( self.log_error(os.path.basename(file), str(
translate('SongsPlugin.PowerSongImport', 'Invalid %s file. Missing "TITLE" header.')) % PS_string) translate('SongsPlugin.PowerSongImport', 'Invalid %s file. Missing "TITLE" header.')) % ps_string)
continue continue
# Check that file had COPYRIGHTLINE label # Check that file had COPYRIGHTLINE label
if not found_copyright: if not found_copyright:
self.log_error(self.title, str( self.log_error(self.title, str(
translate('SongsPlugin.PowerSongImport', 'Invalid %s file. Missing "COPYRIGHTLINE" header.')) % translate('SongsPlugin.PowerSongImport', 'Invalid %s file. Missing "COPYRIGHTLINE" header.')) %
PS_string) ps_string)
continue continue
# Check that file had at least one verse # Check that file had at least one verse
if not self.verses: if not self.verses:
@ -154,14 +155,14 @@ class PowerSongImport(SongImport):
if not self.finish(): if not self.finish():
self.log_error(self.title) self.log_error(self.title)
def _readString(self, file_object): def _read_string(self, file_object):
""" """
Reads in next variable-length string. Reads in next variable-length string.
""" """
string_len = self._read7BitEncodedInteger(file_object) string_len = self._read_7_bit_encoded_integer(file_object)
return str(file_object.read(string_len), 'utf-8', 'ignore') return str(file_object.read(string_len), 'utf-8', 'ignore')
def _read7BitEncodedInteger(self, file_object): def _read_7_bit_encoded_integer(self, file_object):
""" """
Reads in a 32-bit integer in compressed 7-bit format. Reads in a 32-bit integer in compressed 7-bit format.
@ -179,7 +180,7 @@ class PowerSongImport(SongImport):
# Check for corrupted stream (since max 5 bytes per 32-bit integer) # Check for corrupted stream (since max 5 bytes per 32-bit integer)
if i == 5: if i == 5:
raise ValueError raise ValueError
byte = self._readByte(file_object) byte = self._read_byte(file_object)
# Strip high bit and shift left # Strip high bit and shift left
val += (byte & 0x7f) << shift val += (byte & 0x7f) << shift
shift += 7 shift += 7
@ -189,7 +190,7 @@ class PowerSongImport(SongImport):
i += 1 i += 1
return val return val
def _readByte(self, file_object): def _read_byte(self, file_object):
""" """
Reads in next byte as an unsigned integer Reads in next byte as an unsigned integer
@ -202,7 +203,7 @@ class PowerSongImport(SongImport):
else: else:
return ord(byte_str) return ord(byte_str)
def _parseCopyrightCCLI(self, field): def _parse_copyright_cCCLI(self, field):
""" """
Look for CCLI song number, and get copyright Look for CCLI song number, and get copyright
""" """