Change duplicate check to pass int-string tuples to workers, to workaround bug #1388850, also added multiprocessing.freeze_support to __main__ to support multiprocessing in windows builds.

Try to fix DVD 0 track length by waiting. Fixes bug 1387293.
Fix for import of Words of Worship file, bug 1388768, added tests.

bzr-revno: 2440
This commit is contained in:
Tomas Groth 2014-11-07 21:44:09 +00:00 committed by Tim Bentley
commit c16d32dc8a
14 changed files with 179 additions and 40 deletions

View File

@ -28,7 +28,9 @@
############################################################################### ###############################################################################
import sys import sys
import multiprocessing
from openlp.core.common import is_win, is_macosx
from openlp.core import main from openlp.core import main
@ -36,9 +38,13 @@ if __name__ == '__main__':
""" """
Instantiate and run the application. Instantiate and run the application.
""" """
# Add support for using multiprocessing from frozen Windows executable (built using PyInstaller),
# see https://docs.python.org/3/library/multiprocessing.html#multiprocessing.freeze_support
if is_win():
multiprocessing.freeze_support()
# Mac OS X passes arguments like '-psn_XXXX' to the application. This argument is actually a process serial number. # Mac OS X passes arguments like '-psn_XXXX' to the application. This argument is actually a process serial number.
# However, this causes a conflict with other OpenLP arguments. Since we do not use this argument we can delete it # However, this causes a conflict with other OpenLP arguments. Since we do not use this argument we can delete it
# to avoid any potential conflicts. # to avoid any potential conflicts.
if sys.platform.startswith('darwin'): if is_macosx():
sys.argv = [x for x in sys.argv if not x.startswith('-psn')] sys.argv = [x for x in sys.argv if not x.startswith('-psn')]
main() main()

View File

@ -28,4 +28,4 @@
############################################################################### ###############################################################################
""" """
The Projector driver module. The Projector driver module.
""" """

View File

@ -446,6 +446,13 @@ class MediaClipSelectorForm(QtGui.QDialog, Ui_MediaClipSelector, RegistryPropert
# Set media length info # Set media length info
self.playback_length = self.vlc_media_player.get_length() self.playback_length = self.vlc_media_player.get_length()
log.debug('playback_length: %d ms' % self.playback_length) log.debug('playback_length: %d ms' % self.playback_length)
# if length is 0, wait a bit, maybe vlc will change its mind...
loop_count = 0
while self.playback_length == 0 and loop_count < 20:
sleep(0.1)
self.playback_length = self.vlc_media_player.get_length()
loop_count += 1
log.debug('in loop, playback_length: %d ms' % self.playback_length)
self.position_slider.setMaximum(self.playback_length) self.position_slider.setMaximum(self.playback_length)
# setup start and end time # setup start and end time
rounded_vlc_ms_length = int(round(self.playback_length / 100.0) * 100.0) rounded_vlc_ms_length = int(round(self.playback_length / 100.0) * 100.0)

View File

@ -48,14 +48,15 @@ log = logging.getLogger(__name__)
def song_generator(songs): def song_generator(songs):
""" """
This is a generator function to return tuples of two songs. When completed then all songs have once been returned This is a generator function to return tuples of tuple with two songs and their position in the song array.
combined with any other songs. When completed then all songs have once been returned combined with any other songs.
:param songs: All songs in the database. :param songs: All songs in the database.
""" """
for outer_song_counter in range(len(songs) - 1): for outer_song_counter in range(len(songs) - 1):
for inner_song_counter in range(outer_song_counter + 1, len(songs)): for inner_song_counter in range(outer_song_counter + 1, len(songs)):
yield (songs[outer_song_counter], songs[inner_song_counter]) yield ((outer_song_counter, songs[outer_song_counter].search_lyrics),
(inner_song_counter, songs[inner_song_counter].search_lyrics))
class DuplicateSongRemovalForm(OpenLPWizard, RegistryProperties): class DuplicateSongRemovalForm(OpenLPWizard, RegistryProperties):
@ -187,16 +188,17 @@ class DuplicateSongRemovalForm(OpenLPWizard, RegistryProperties):
# Do not accept any further tasks. Also this closes the processes if all tasks are done. # Do not accept any further tasks. Also this closes the processes if all tasks are done.
pool.close() pool.close()
# While the processes are still working, start to look at the results. # While the processes are still working, start to look at the results.
for song_tuple in result: for pos_tuple in result:
self.duplicate_search_progress_bar.setValue(self.duplicate_search_progress_bar.value() + 1) self.duplicate_search_progress_bar.setValue(self.duplicate_search_progress_bar.value() + 1)
# The call to process_events() will keep the GUI responsive. # The call to process_events() will keep the GUI responsive.
self.application.process_events() self.application.process_events()
if self.break_search: if self.break_search:
pool.terminate() pool.terminate()
return return
if song_tuple is None: if pos_tuple is None:
continue continue
song1, song2 = song_tuple song1 = songs[pos_tuple[0]]
song2 = songs[pos_tuple[1]]
duplicate_added = self.add_duplicates_to_song_list(song1, song2) duplicate_added = self.add_duplicates_to_song_list(song1, song2)
if duplicate_added: if duplicate_added:
self.found_duplicates_edit.appendPlainText(song1.title + " = " + song2.title) self.found_duplicates_edit.appendPlainText(song1.title + " = " + song2.title)

View File

@ -99,7 +99,7 @@ class WordsOfWorshipImport(SongImport):
""" """
Initialise the Words of Worship importer. Initialise the Words of Worship importer.
""" """
SongImport.__init__(self, manager, **kwargs) super(WordsOfWorshipImport, self).__init__(manager, **kwargs)
def do_import(self): def do_import(self):
""" """
@ -112,17 +112,17 @@ class WordsOfWorshipImport(SongImport):
return return
self.set_defaults() self.set_defaults()
song_data = open(source, 'rb') song_data = open(source, 'rb')
if song_data.read(19) != 'WoW File\nSong Words': if song_data.read(19).decode() != 'WoW File\nSong Words':
self.log_error(source, self.log_error(source,
str(translate('SongsPlugin.WordsofWorshipSongImport', str(translate('SongsPlugin.WordsofWorshipSongImport',
'Invalid Words of Worship song file. Missing "Wow File\\nSong ' 'Invalid Words of Worship song file. Missing "WoW File\\nSong '
'Words" header.'))) 'Words" header.')))
continue continue
# Seek to byte which stores number of blocks in the song # Seek to byte which stores number of blocks in the song
song_data.seek(56) song_data.seek(56)
no_of_blocks = ord(song_data.read(1)) no_of_blocks = ord(song_data.read(1))
song_data.seek(66) song_data.seek(66)
if song_data.read(16) != 'CSongDoc::CBlock': if song_data.read(16).decode() != 'CSongDoc::CBlock':
self.log_error(source, self.log_error(source,
str(translate('SongsPlugin.WordsofWorshipSongImport', str(translate('SongsPlugin.WordsofWorshipSongImport',
'Invalid Words of Worship song file. Missing "CSongDoc::CBlock" ' 'Invalid Words of Worship song file. Missing "CSongDoc::CBlock" '
@ -131,11 +131,17 @@ class WordsOfWorshipImport(SongImport):
# Seek to the beginning of the first block # Seek to the beginning of the first block
song_data.seek(82) song_data.seek(82)
for block in range(no_of_blocks): for block in range(no_of_blocks):
skip_char_at_end = True
self.lines_to_read = ord(song_data.read(4)[:1]) self.lines_to_read = ord(song_data.read(4)[:1])
block_text = '' block_text = ''
while self.lines_to_read: while self.lines_to_read:
self.line_text = str(song_data.read(ord(song_data.read(1))), 'cp1252') self.line_text = str(song_data.read(ord(song_data.read(1))), 'cp1252')
song_data.seek(1, os.SEEK_CUR) if skip_char_at_end:
skip_char = ord(song_data.read(1))
# Check if we really should skip a char. In some wsg files we shouldn't
if skip_char != 0:
song_data.seek(-1, os.SEEK_CUR)
skip_char_at_end = False
if block_text: if block_text:
block_text += '\n' block_text += '\n'
block_text += self.line_text block_text += self.line_text

View File

@ -59,12 +59,14 @@ def songs_probably_equal(song_tupel):
:param song_tupel: A tuple of two songs to compare. :param song_tupel: A tuple of two songs to compare.
""" """
song1, song2 = song_tupel song1, song2 = song_tupel
if len(song1.search_lyrics) < len(song2.search_lyrics): pos1, lyrics1 = song1
small = song1.search_lyrics pos2, lyrics2 = song2
large = song2.search_lyrics if len(lyrics1) < len(lyrics2):
small = lyrics1
large = lyrics2
else: else:
small = song2.search_lyrics small = lyrics2
large = song1.search_lyrics large = lyrics1
differ = difflib.SequenceMatcher(a=large, b=small) differ = difflib.SequenceMatcher(a=large, b=small)
diff_tuples = differ.get_opcodes() diff_tuples = differ.get_opcodes()
diff_no_typos = _remove_typos(diff_tuples) diff_no_typos = _remove_typos(diff_tuples)
@ -77,7 +79,7 @@ def songs_probably_equal(song_tupel):
length_of_equal_blocks += _op_length(element) length_of_equal_blocks += _op_length(element)
if length_of_equal_blocks >= MIN_BLOCK_SIZE: if length_of_equal_blocks >= MIN_BLOCK_SIZE:
return song1, song2 return pos1, pos2
# Check 2: Similarity based on the relative length of the longest equal block. # Check 2: Similarity based on the relative length of the longest equal block.
# Calculate the length of the largest equal block of the diff set. # Calculate the length of the largest equal block of the diff set.
length_of_longest_equal_block = 0 length_of_longest_equal_block = 0
@ -85,7 +87,7 @@ def songs_probably_equal(song_tupel):
if element[0] == "equal" and _op_length(element) > length_of_longest_equal_block: if element[0] == "equal" and _op_length(element) > length_of_longest_equal_block:
length_of_longest_equal_block = _op_length(element) length_of_longest_equal_block = _op_length(element)
if length_of_longest_equal_block > len(small) * 2 // 3: if length_of_longest_equal_block > len(small) * 2 // 3:
return song1, song2 return pos1, pos2
# Both checks failed. We assume the songs are not equal. # Both checks failed. We assume the songs are not equal.
return None return None

View File

@ -157,4 +157,4 @@ class TestSettingsForm(TestCase):
# THEN: The general tab's cancel() method should have been called, but not the themes tab # THEN: The general tab's cancel() method should have been called, but not the themes tab
mocked_general_cancel.assert_called_with() mocked_general_cancel.assert_called_with()
self.assertEqual(0, mocked_theme_cancel.call_count, 'The Themes tab\'s cancel() should not have been called') self.assertEqual(0, mocked_theme_cancel.call_count, 'The Themes tab\'s cancel() should not have been called')

View File

@ -95,4 +95,4 @@ class TestImageMediaItem(TestCase, TestMixin):
self.form.save() self.form.save()
# THEN: the post process should be requested # THEN: the post process should be requested
self.assertEqual(1, self.form.settings_form.register_post_process.call_count, self.assertEqual(1, self.form.settings_form.register_post_process.call_count,
'Image Post processing should have been requested') 'Image Post processing should have been requested')

View File

@ -58,8 +58,6 @@ class TestLib(TestCase):
i love that old cross where the dearest and best for a world of lost sinners was slain so ill cherish the i love that old cross where the dearest and best for a world of lost sinners was slain so ill cherish the
old rugged cross till my trophies at last i lay down i will cling to the old rugged cross and exchange it old rugged cross till my trophies at last i lay down i will cling to the old rugged cross and exchange it
some day for a crown''' some day for a crown'''
self.song1 = MagicMock()
self.song2 = MagicMock()
def clean_string_test(self): def clean_string_test(self):
""" """
@ -92,53 +90,53 @@ class TestLib(TestCase):
Test the songs_probably_equal function with twice the same song. Test the songs_probably_equal function with twice the same song.
""" """
# GIVEN: Two equal songs. # GIVEN: Two equal songs.
self.song1.search_lyrics = self.full_lyrics song_tuple1 = (2, self.full_lyrics)
self.song2.search_lyrics = self.full_lyrics song_tuple2 = (4, self.full_lyrics)
# WHEN: We compare those songs for equality. # WHEN: We compare those songs for equality.
result = songs_probably_equal((self.song1, self.song2)) result = songs_probably_equal((song_tuple1, song_tuple2))
# THEN: The result should be a tuple.. # THEN: The result should be a tuple..
assert result == (self.song1, self.song2), 'The result should be the tuble of songs' assert result == (2, 4), 'The result should be the tuble of song positions'
def songs_probably_equal_short_song_test(self): def songs_probably_equal_short_song_test(self):
""" """
Test the songs_probably_equal function with a song and a shorter version of the same song. Test the songs_probably_equal function with a song and a shorter version of the same song.
""" """
# GIVEN: A song and a short version of the same song. # GIVEN: A song and a short version of the same song.
self.song1.search_lyrics = self.full_lyrics song_tuple1 = (1, self.full_lyrics)
self.song2.search_lyrics = self.short_lyrics song_tuple2 = (3, self.short_lyrics)
# WHEN: We compare those songs for equality. # WHEN: We compare those songs for equality.
result = songs_probably_equal((self.song1, self.song2)) result = songs_probably_equal((song_tuple1, song_tuple2))
# THEN: The result should be a tuple.. # THEN: The result should be a tuple..
assert result == (self.song1, self.song2), 'The result should be the tuble of songs' assert result == (1, 3), 'The result should be the tuble of song positions'
def songs_probably_equal_error_song_test(self): def songs_probably_equal_error_song_test(self):
""" """
Test the songs_probably_equal function with a song and a very erroneous version of the same song. Test the songs_probably_equal function with a song and a very erroneous version of the same song.
""" """
# GIVEN: A song and the same song with lots of errors. # GIVEN: A song and the same song with lots of errors.
self.song1.search_lyrics = self.full_lyrics song_tuple1 = (4, self.full_lyrics)
self.song2.search_lyrics = self.error_lyrics song_tuple2 = (7, self.error_lyrics)
# WHEN: We compare those songs for equality. # WHEN: We compare those songs for equality.
result = songs_probably_equal((self.song1, self.song2)) result = songs_probably_equal((song_tuple1, song_tuple2))
# THEN: The result should be a tuple of songs.. # THEN: The result should be a tuple of song positions.
assert result == (self.song1, self.song2), 'The result should be the tuble of songs' assert result == (4, 7), 'The result should be the tuble of song positions'
def songs_probably_equal_different_song_test(self): def songs_probably_equal_different_song_test(self):
""" """
Test the songs_probably_equal function with two different songs. Test the songs_probably_equal function with two different songs.
""" """
# GIVEN: Two different songs. # GIVEN: Two different songs.
self.song1.search_lyrics = self.full_lyrics song_tuple1 = (5, self.full_lyrics)
self.song2.search_lyrics = self.different_lyrics song_tuple2 = (8, self.different_lyrics)
# WHEN: We compare those songs for equality. # WHEN: We compare those songs for equality.
result = songs_probably_equal((self.song1, self.song2)) result = songs_probably_equal((song_tuple1, song_tuple2))
# THEN: The result should be None. # THEN: The result should be None.
assert result is None, 'The result should be None' assert result is None, 'The result should be None'

View File

@ -0,0 +1,56 @@
# -*- 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 #
###############################################################################
"""
This module contains tests for the Words of Worship song importer.
"""
import os
from tests.helpers.songfileimport import SongImportTestHelper
from openlp.plugins.songs.lib.importers.wordsofworship import WordsOfWorshipImport
TEST_PATH = os.path.abspath(
os.path.join(os.path.dirname(__file__), '..', '..', '..', 'resources', 'wordsofworshipsongs'))
class TestWordsOfWorshipFileImport(SongImportTestHelper):
def __init__(self, *args, **kwargs):
self.importer_class_name = 'WordsOfWorshipImport'
self.importer_module_name = 'wordsofworship'
super(TestWordsOfWorshipFileImport, self).__init__(*args, **kwargs)
def test_song_import(self):
"""
Test that loading a Words of Worship file works correctly
"""
self.file_import([os.path.join(TEST_PATH, 'Amazing Grace (6 Verses).wow-song')],
self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace (6 Verses).json')))
self.file_import([os.path.join(TEST_PATH, 'When morning gilds the skies.wsg')],
self.load_external_result_data(os.path.join(TEST_PATH, 'When morning gilds the skies.json')))

View File

@ -0,0 +1,33 @@
{
"authors": [
"John Newton (1725-1807)"
],
"title": "Amazing Grace (6 Verses)",
"verse_order_list": [],
"verses": [
[
"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.",
"V"
],
[
"'Twas grace that taught my heart to fear,\nAnd grace my fears relieved;\nHow precious did that grace appear,\nThe hour I first believed!",
"V"
],
[
"Through many dangers, toils and snares\nI have already come;\n'Tis grace that brought me safe thus far,\nAnd grace will lead me home.",
"V"
],
[
"The Lord has promised good to me,\nHis word my hope secures;\nHe will my shield and portion be\nAs long as life endures.",
"V"
],
[
"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.",
"V"
],
[
"When we've been there ten thousand years,\nBright shining as the sun,\nWe've no less days to sing God's praise\nThan when we first begun.",
"V"
]
]
}

View File

@ -0,0 +1,29 @@
{
"authors": [
"Author Unknown. Tr. Edward Caswall"
],
"title": "When morning gilds the skies",
"verse_order_list": [],
"verses": [
[
"When morning gilds the skies\nMy heart awaking cries:\n'May Jesus Christ be prais'd!'\nAlike at work and prayer to Jesus I repair:\n'May Jesus Christ be prais'd!'",
"V"
],
[
"Does sadness fill my mind?\nA solace here I find:\n'May Jesus Christ be praised!'\nWhen evil thoughts molest,\nWith this I shield my breast:\n'May Jesus Christ be prais'd!'",
"V"
],
[
"To God, the Word, on high\nThe hosts of angels cry:\n'May Jesus Christ be prais'd!'\nLet mortals, too, upraise\nTheir voice in hymns of praise:\n'May Jesus Christ be prais'd!'",
"V"
],
[
"Let earth's wide circle round\nIn joyful notes resound:\n'May Jesus Christ be prais'd!'\nLet air, and sea, and sky,\nFrom depth to height, reply:\n'May Jesus Christ be prais'd!'",
"V"
],
[
"Be this while life is mine\nMy canticle divine\n'May Jesus Christ be prais'd!'\nBe this the eternal song,\nThrough all the ages long:\n'May Jesus Christ be prais'd!'",
"V"
]
]
}