forked from openlp/openlp
started clean up of song importers
This commit is contained in:
commit
0398b93f78
@ -740,13 +740,16 @@ class SongImportForm(OpenLPWizard):
|
|||||||
importer = self.plugin.importSongs(SongFormat.FoilPresenter,
|
importer = self.plugin.importSongs(SongFormat.FoilPresenter,
|
||||||
filenames=self.getListOfFiles(self.foilPresenterFileListWidget)
|
filenames=self.getListOfFiles(self.foilPresenterFileListWidget)
|
||||||
)
|
)
|
||||||
message = importer.do_import()
|
test = importer.do_import()
|
||||||
if isinstance(message, bool) and not message:
|
if isinstance(test, bool):
|
||||||
|
raise received_boolean
|
||||||
|
if importer.stop_import_flag:
|
||||||
|
print importer.import_error_log
|
||||||
|
print u'cancelled'
|
||||||
|
elif importer.import_error_log:
|
||||||
self.progressLabel.setText(self.progressLabel.setText(
|
self.progressLabel.setText(self.progressLabel.setText(
|
||||||
translate('SongsPlugin.SongImportForm',
|
translate('SongsPlugin.SongImportForm',
|
||||||
'Your song import failed.')))
|
'Your song import failed.')))
|
||||||
elif not isinstance(message, bool) and message:
|
|
||||||
self.progressLabel.setText(message)
|
|
||||||
else:
|
else:
|
||||||
self.progressLabel.setText(WizardStrings.FinishedImport)
|
self.progressLabel.setText(WizardStrings.FinishedImport)
|
||||||
|
|
||||||
|
@ -59,16 +59,10 @@ class CCLIFileImport(SongImport):
|
|||||||
Import either a ``.usr`` or a ``.txt`` SongSelect file.
|
Import either a ``.usr`` or a ``.txt`` SongSelect file.
|
||||||
"""
|
"""
|
||||||
log.debug(u'Starting CCLI File Import')
|
log.debug(u'Starting CCLI File Import')
|
||||||
song_total = len(self.import_source)
|
self.import_wizard.progressBar.setMaximum(len(self.import_source))
|
||||||
self.import_wizard.progressBar.setMaximum(song_total)
|
|
||||||
song_count = 1
|
|
||||||
for filename in self.import_source:
|
for filename in self.import_source:
|
||||||
self.import_wizard.incrementProgressBar(unicode(translate(
|
|
||||||
'SongsPlugin.CCLIFileImport', 'Importing song %d of %d')) %
|
|
||||||
(song_count, song_total))
|
|
||||||
filename = unicode(filename)
|
filename = unicode(filename)
|
||||||
log.debug(u'Importing CCLI File: %s', filename)
|
log.debug(u'Importing CCLI File: %s', filename)
|
||||||
self.set_defaults()
|
|
||||||
lines = []
|
lines = []
|
||||||
if os.path.isfile(filename):
|
if os.path.isfile(filename):
|
||||||
detect_file = open(filename, u'r')
|
detect_file = open(filename, u'r')
|
||||||
@ -81,6 +75,7 @@ class CCLIFileImport(SongImport):
|
|||||||
detect_file.close()
|
detect_file.close()
|
||||||
infile = codecs.open(filename, u'r', details['encoding'])
|
infile = codecs.open(filename, u'r', details['encoding'])
|
||||||
lines = infile.readlines()
|
lines = infile.readlines()
|
||||||
|
infile.close()
|
||||||
ext = os.path.splitext(filename)[1]
|
ext = os.path.splitext(filename)[1]
|
||||||
if ext.lower() == u'.usr':
|
if ext.lower() == u'.usr':
|
||||||
log.info(u'SongSelect .usr format file found: %s', filename)
|
log.info(u'SongSelect .usr format file found: %s', filename)
|
||||||
@ -89,10 +84,12 @@ class CCLIFileImport(SongImport):
|
|||||||
log.info(u'SongSelect .txt format file found: %s', filename)
|
log.info(u'SongSelect .txt format file found: %s', filename)
|
||||||
self.do_import_txt_file(lines)
|
self.do_import_txt_file(lines)
|
||||||
else:
|
else:
|
||||||
|
self.log_error(filename,
|
||||||
|
translate('SongsPlugin.CCLIFileImport',
|
||||||
|
'The file does not have a valid extension.'))
|
||||||
log.info(u'Extension %s is not valid', filename)
|
log.info(u'Extension %s is not valid', filename)
|
||||||
song_count += 1
|
|
||||||
if self.stop_import_flag:
|
if self.stop_import_flag:
|
||||||
return False
|
return
|
||||||
|
|
||||||
def do_import_usr_file(self, textList):
|
def do_import_usr_file(self, textList):
|
||||||
"""
|
"""
|
||||||
@ -333,6 +330,5 @@ class CCLIFileImport(SongImport):
|
|||||||
if len(author_list) < 2:
|
if len(author_list) < 2:
|
||||||
author_list = song_author.split(u'|')
|
author_list = song_author.split(u'|')
|
||||||
# Clean spaces before and after author names.
|
# Clean spaces before and after author names.
|
||||||
for author_name in author_list:
|
[self.add_author(author_name.strip()) for author_name in author_list]
|
||||||
self.add_author(author_name.strip())
|
|
||||||
self.finish()
|
self.finish()
|
||||||
|
@ -26,11 +26,13 @@
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
from lxml import etree, objectify
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
from lxml import etree, objectify
|
||||||
|
|
||||||
from openlp.core.lib import translate
|
from openlp.core.lib import translate
|
||||||
from openlp.core.ui.wizard import WizardStrings
|
from openlp.core.ui.wizard import WizardStrings
|
||||||
|
from openlp.plugins.songs.lib import VerseType
|
||||||
from openlp.plugins.songs.lib.songimport import SongImport
|
from openlp.plugins.songs.lib.songimport import SongImport
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
@ -56,27 +58,19 @@ class EasiSlidesImport(SongImport):
|
|||||||
multiple opensong files. If `self.commit` is set False, the
|
multiple opensong files. If `self.commit` is set False, the
|
||||||
import will not be committed to the database (useful for test scripts).
|
import will not be committed to the database (useful for test scripts).
|
||||||
"""
|
"""
|
||||||
self.import_wizard.progressBar.setMaximum(1)
|
|
||||||
log.info(u'Importing EasiSlides XML file %s', self.import_source)
|
log.info(u'Importing EasiSlides XML file %s', self.import_source)
|
||||||
parser = etree.XMLParser(remove_blank_text=True)
|
parser = etree.XMLParser(remove_blank_text=True)
|
||||||
file = etree.parse(self.import_source, parser)
|
file = etree.parse(self.import_source, parser)
|
||||||
xml = unicode(etree.tostring(file))
|
xml = unicode(etree.tostring(file))
|
||||||
song_xml = objectify.fromstring(xml)
|
song_xml = objectify.fromstring(xml)
|
||||||
self.import_wizard.incrementProgressBar(
|
|
||||||
WizardStrings.ImportingType % os.path.split(self.import_source)[-1])
|
|
||||||
self.import_wizard.progressBar.setMaximum(len(song_xml.Item))
|
self.import_wizard.progressBar.setMaximum(len(song_xml.Item))
|
||||||
for song in song_xml.Item:
|
for song in song_xml.Item:
|
||||||
self.import_wizard.incrementProgressBar(
|
if self.stop_import_flag:
|
||||||
unicode(translate('SongsPlugin.ImportWizardForm',
|
return
|
||||||
u'Importing %s, song %s...')) %
|
self._parse_song(song)
|
||||||
(os.path.split(self.import_source)[-1], song.Title1))
|
|
||||||
success = self._parse_song(song)
|
|
||||||
if not success or self.stop_import_flag:
|
|
||||||
return False
|
|
||||||
elif self.commit:
|
|
||||||
self.finish()
|
|
||||||
|
|
||||||
def _parse_song(self, song):
|
def _parse_song(self, song):
|
||||||
|
self.set_defaults()
|
||||||
self._success = True
|
self._success = True
|
||||||
self._add_unicode_attribute(u'title', song.Title1, True)
|
self._add_unicode_attribute(u'title', song.Title1, True)
|
||||||
self._add_unicode_attribute(u'alternate_title', song.Title2)
|
self._add_unicode_attribute(u'alternate_title', song.Title2)
|
||||||
@ -89,7 +83,8 @@ class EasiSlidesImport(SongImport):
|
|||||||
self._add_copyright(song.LicenceAdmin2)
|
self._add_copyright(song.LicenceAdmin2)
|
||||||
self._add_unicode_attribute(u'song_book_name', song.BookReference)
|
self._add_unicode_attribute(u'song_book_name', song.BookReference)
|
||||||
self._parse_and_add_lyrics(song)
|
self._parse_and_add_lyrics(song)
|
||||||
return self._success
|
if self._success:
|
||||||
|
self.finish()
|
||||||
|
|
||||||
def _add_unicode_attribute(self, self_attribute, import_attribute,
|
def _add_unicode_attribute(self, self_attribute, import_attribute,
|
||||||
mandatory=False):
|
mandatory=False):
|
||||||
@ -187,12 +182,13 @@ class EasiSlidesImport(SongImport):
|
|||||||
# if the regions are inside verses
|
# if the regions are inside verses
|
||||||
regionsInVerses = (regions and regionlines[regionlines.keys()[0]] > 1)
|
regionsInVerses = (regions and regionlines[regionlines.keys()[0]] > 1)
|
||||||
MarkTypes = {
|
MarkTypes = {
|
||||||
u'CHORUS': u'C',
|
u'CHORUS': VerseType.Tags[VerseType.Chorus],
|
||||||
u'VERSE': u'V',
|
u'VERSE': VerseType.Tags[VerseType.Verse],
|
||||||
u'INTRO': u'I',
|
u'INTRO': VerseType.Tags[VerseType.Intro],
|
||||||
u'ENDING': u'E',
|
u'ENDING': VerseType.Tags[VerseType.Ending],
|
||||||
u'BRIDGE': u'B',
|
u'BRIDGE': VerseType.Tags[VerseType.Bridge],
|
||||||
u'PRECHORUS': u'P'}
|
u'PRECHORUS': VerseType.Tags[VerseType.PreChorus]
|
||||||
|
}
|
||||||
verses = {}
|
verses = {}
|
||||||
# list as [region, versetype, versenum, instance]
|
# list as [region, versetype, versenum, instance]
|
||||||
our_verse_order = []
|
our_verse_order = []
|
||||||
|
@ -33,6 +33,7 @@ import struct
|
|||||||
|
|
||||||
from openlp.core.lib import translate
|
from openlp.core.lib import translate
|
||||||
from openlp.core.ui.wizard import WizardStrings
|
from openlp.core.ui.wizard import WizardStrings
|
||||||
|
from openlp.plugins.songs.lib import VerseType
|
||||||
from openlp.plugins.songs.lib import retrieve_windows_encoding
|
from openlp.plugins.songs.lib import retrieve_windows_encoding
|
||||||
from songimport import SongImport
|
from songimport import SongImport
|
||||||
|
|
||||||
@ -203,8 +204,8 @@ class EasyWorshipSongImport(SongImport):
|
|||||||
field_size))
|
field_size))
|
||||||
self.set_record_struct(field_descs)
|
self.set_record_struct(field_descs)
|
||||||
# Pick out the field description indexes we will need
|
# Pick out the field description indexes we will need
|
||||||
success = True
|
|
||||||
try:
|
try:
|
||||||
|
success = True
|
||||||
fi_title = self.find_field(u'Title')
|
fi_title = self.find_field(u'Title')
|
||||||
fi_author = self.find_field(u'Author')
|
fi_author = self.find_field(u'Author')
|
||||||
fi_copy = self.find_field(u'Copyright')
|
fi_copy = self.find_field(u'Copyright')
|
||||||
@ -223,24 +224,18 @@ class EasyWorshipSongImport(SongImport):
|
|||||||
# Loop through each record within the current block
|
# Loop through each record within the current block
|
||||||
for i in range(rec_count):
|
for i in range(rec_count):
|
||||||
if self.stop_import_flag:
|
if self.stop_import_flag:
|
||||||
success = False
|
|
||||||
break
|
break
|
||||||
raw_record = db_file.read(record_size)
|
raw_record = db_file.read(record_size)
|
||||||
self.fields = self.record_struct.unpack(raw_record)
|
self.fields = self.record_struct.unpack(raw_record)
|
||||||
self.set_defaults()
|
self.set_defaults()
|
||||||
# Get title and update progress bar message
|
self.title = self.get_field(fi_title)
|
||||||
title = self.get_field(fi_title)
|
# Get remaining fields.
|
||||||
if title:
|
|
||||||
self.import_wizard.incrementProgressBar(
|
|
||||||
WizardStrings.ImportingType % title, 0)
|
|
||||||
self.title = title
|
|
||||||
# Get remaining fields
|
|
||||||
copy = self.get_field(fi_copy)
|
copy = self.get_field(fi_copy)
|
||||||
admin = self.get_field(fi_admin)
|
admin = self.get_field(fi_admin)
|
||||||
ccli = self.get_field(fi_ccli)
|
ccli = self.get_field(fi_ccli)
|
||||||
authors = self.get_field(fi_author)
|
authors = self.get_field(fi_author)
|
||||||
words = self.get_field(fi_words)
|
words = self.get_field(fi_words)
|
||||||
# Set the SongImport object members
|
# Set the SongImport object members.
|
||||||
if copy:
|
if copy:
|
||||||
self.copyright = copy
|
self.copyright = copy
|
||||||
if admin:
|
if admin:
|
||||||
@ -264,17 +259,13 @@ class EasyWorshipSongImport(SongImport):
|
|||||||
# Format the lyrics
|
# Format the lyrics
|
||||||
words = strip_rtf(words, self.encoding)
|
words = strip_rtf(words, self.encoding)
|
||||||
for verse in words.split(u'\n\n'):
|
for verse in words.split(u'\n\n'):
|
||||||
self.add_verse(verse.strip(), u'V')
|
self.add_verse(
|
||||||
|
verse.strip(), VerseType.Tags[VerseType.Verse])
|
||||||
if self.stop_import_flag:
|
if self.stop_import_flag:
|
||||||
success = False
|
|
||||||
break
|
break
|
||||||
self.finish()
|
self.finish()
|
||||||
if not self.stop_import_flag:
|
|
||||||
self.import_wizard.incrementProgressBar(u'')
|
|
||||||
db_file.close()
|
db_file.close()
|
||||||
self.memo_file.close()
|
self.memo_file.close()
|
||||||
if not success:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def find_field(self, field_name):
|
def find_field(self, field_name):
|
||||||
return [i for i, x in enumerate(self.field_descs) \
|
return [i for i, x in enumerate(self.field_descs) \
|
||||||
|
@ -121,7 +121,7 @@ class FoilPresenterImport(SongImport):
|
|||||||
parser = etree.XMLParser(remove_blank_text=True)
|
parser = etree.XMLParser(remove_blank_text=True)
|
||||||
for file_path in self.import_source:
|
for file_path in self.import_source:
|
||||||
if self.stop_import_flag:
|
if self.stop_import_flag:
|
||||||
return False
|
return
|
||||||
self.import_wizard.incrementProgressBar(
|
self.import_wizard.incrementProgressBar(
|
||||||
WizardStrings.ImportingType % os.path.basename(file_path))
|
WizardStrings.ImportingType % os.path.basename(file_path))
|
||||||
try:
|
try:
|
||||||
@ -303,9 +303,8 @@ class FoilPresenter(object):
|
|||||||
for marker in markers:
|
for marker in markers:
|
||||||
copyright = re.compile(marker).sub(u'<marker>', copyright, re.U)
|
copyright = re.compile(marker).sub(u'<marker>', copyright, re.U)
|
||||||
copyright = re.compile(u'(?<=<marker>) *:').sub(u'', copyright)
|
copyright = re.compile(u'(?<=<marker>) *:').sub(u'', copyright)
|
||||||
i = 0
|
|
||||||
x = 0
|
x = 0
|
||||||
while i != 1:
|
while True:
|
||||||
if copyright.find(u'<marker>') != -1:
|
if copyright.find(u'<marker>') != -1:
|
||||||
temp = copyright.partition(u'<marker>')
|
temp = copyright.partition(u'<marker>')
|
||||||
if temp[0].strip() and x > 0:
|
if temp[0].strip() and x > 0:
|
||||||
@ -314,9 +313,9 @@ class FoilPresenter(object):
|
|||||||
x += 1
|
x += 1
|
||||||
elif x > 0:
|
elif x > 0:
|
||||||
strings.append(copyright)
|
strings.append(copyright)
|
||||||
i = 1
|
break
|
||||||
else:
|
else:
|
||||||
i = 1
|
break
|
||||||
author_temp = []
|
author_temp = []
|
||||||
for author in strings:
|
for author in strings:
|
||||||
temp = re.split(u',(?=\D{2})|(?<=\D),|\/(?=\D{3,})|(?<=\D);',
|
temp = re.split(u',(?=\D{2})|(?<=\D),|\/(?=\D{3,})|(?<=\D);',
|
||||||
@ -347,8 +346,8 @@ class FoilPresenter(object):
|
|||||||
if author is None:
|
if author is None:
|
||||||
# We need to create a new author, as the author does not exist.
|
# We need to create a new author, as the author does not exist.
|
||||||
author = Author.populate(display_name=display_name,
|
author = Author.populate(display_name=display_name,
|
||||||
last_name = display_name.split(u' ')[-1],
|
last_name=display_name.split(u' ')[-1],
|
||||||
first_name = u' '.join(display_name.split(u' ')[:-1]))
|
first_name=u' '.join(display_name.split(u' ')[:-1]))
|
||||||
self.manager.save_object(author)
|
self.manager.save_object(author)
|
||||||
song.authors.append(author)
|
song.authors.append(author)
|
||||||
|
|
||||||
@ -412,8 +411,15 @@ class FoilPresenter(object):
|
|||||||
temp_verse_order_backup = []
|
temp_verse_order_backup = []
|
||||||
temp_sortnr_backup = 1
|
temp_sortnr_backup = 1
|
||||||
temp_sortnr_liste = []
|
temp_sortnr_liste = []
|
||||||
versenumber = {u'V': 1, u'C': 1, u'B': 1, u'E': 1, u'O': 1, u'I': 1,
|
versenumber = {
|
||||||
u'P': 1}
|
VerseType.Tags[VerseType.Verse]: 1,
|
||||||
|
VerseType.Tags[VerseType.Chorus]: 1,
|
||||||
|
VerseType.Tags[VerseType.Bridge]: 1,
|
||||||
|
VerseType.Tags[VerseType.Ending]: 1,
|
||||||
|
VerseType.Tags[VerseType.Other]: 1,
|
||||||
|
VerseType.Tags[VerseType.Intro]: 1,
|
||||||
|
VerseType.Tags[VerseType.PreChorus]: 1
|
||||||
|
}
|
||||||
for strophe in foilpresenterfolie.strophen.strophe:
|
for strophe in foilpresenterfolie.strophen.strophe:
|
||||||
text = self._child(strophe.text_)
|
text = self._child(strophe.text_)
|
||||||
verse_name = self._child(strophe.key)
|
verse_name = self._child(strophe.key)
|
||||||
@ -432,25 +438,25 @@ class FoilPresenter(object):
|
|||||||
temp_verse_name = re.compile(u'[0-9].*').sub(u'', verse_name)
|
temp_verse_name = re.compile(u'[0-9].*').sub(u'', verse_name)
|
||||||
temp_verse_name = temp_verse_name[:3].lower()
|
temp_verse_name = temp_verse_name[:3].lower()
|
||||||
if temp_verse_name == u'ref':
|
if temp_verse_name == u'ref':
|
||||||
verse_type = u'C'
|
verse_type = VerseType.Tags[VerseType.Chorus]
|
||||||
elif temp_verse_name == u'r':
|
elif temp_verse_name == u'r':
|
||||||
verse_type = u'C'
|
verse_type = VerseType.Tags[VerseType.Chorus]
|
||||||
elif temp_verse_name == u'':
|
elif temp_verse_name == u'':
|
||||||
verse_type = u'V'
|
verse_type = VerseType.Tags[VerseType.Verse]
|
||||||
elif temp_verse_name == u'v':
|
elif temp_verse_name == u'v':
|
||||||
verse_type = u'V'
|
verse_type = VerseType.Tags[VerseType.Verse]
|
||||||
elif temp_verse_name == u'bri':
|
elif temp_verse_name == u'bri':
|
||||||
verse_type = u'B'
|
verse_type = VerseType.Tags[VerseType.Bridge]
|
||||||
elif temp_verse_name == u'cod':
|
elif temp_verse_name == u'cod':
|
||||||
verse_type = u'E'
|
verse_type = VerseType.Tags[VerseType.Ending]
|
||||||
elif temp_verse_name == u'sch':
|
elif temp_verse_name == u'sch':
|
||||||
verse_type = u'E'
|
verse_type = VerseType.Tags[VerseType.Ending]
|
||||||
elif temp_verse_name == u'pre':
|
elif temp_verse_name == u'pre':
|
||||||
verse_type = u'P'
|
verse_type = VerseType.Tags[VerseType.PreChorus]
|
||||||
elif temp_verse_name == u'int':
|
elif temp_verse_name == u'int':
|
||||||
verse_type = u'I'
|
verse_type = VerseType.Tags[VerseType.Intro]
|
||||||
else:
|
else:
|
||||||
verse_type = u'O'
|
verse_type = VerseType.Tags[VerseType.Other]
|
||||||
verse_number = re.compile(u'[a-zA-Z.+-_ ]*').sub(u'', verse_name)
|
verse_number = re.compile(u'[a-zA-Z.+-_ ]*').sub(u'', verse_name)
|
||||||
# Foilpresenter allows e. g. "C", but we need "C1".
|
# Foilpresenter allows e. g. "C", but we need "C1".
|
||||||
if not verse_number:
|
if not verse_number:
|
||||||
@ -464,8 +470,8 @@ class FoilPresenter(object):
|
|||||||
verse_number = unicode(int(verse_number) + 1)
|
verse_number = unicode(int(verse_number) + 1)
|
||||||
verse_type_index = VerseType.from_tag(verse_type[0])
|
verse_type_index = VerseType.from_tag(verse_type[0])
|
||||||
verse_type = VerseType.Names[verse_type_index]
|
verse_type = VerseType.Names[verse_type_index]
|
||||||
temp_verse_order[verse_sortnr] = (u''.join((verse_type[0],
|
temp_verse_order[verse_sortnr] = u''.join((verse_type[0],
|
||||||
verse_number)))
|
verse_number))
|
||||||
temp_verse_order_backup.append(u''.join((verse_type[0],
|
temp_verse_order_backup.append(u''.join((verse_type[0],
|
||||||
verse_number)))
|
verse_number)))
|
||||||
sxml.add_verse_to_lyrics(verse_type, verse_number, text)
|
sxml.add_verse_to_lyrics(verse_type, verse_number, text)
|
||||||
|
@ -63,12 +63,18 @@ class OpenLP1SongImport(SongImport):
|
|||||||
Run the import for an openlp.org 1.x song database.
|
Run the import for an openlp.org 1.x song database.
|
||||||
"""
|
"""
|
||||||
if not self.import_source.endswith(u'.olp'):
|
if not self.import_source.endswith(u'.olp'):
|
||||||
return translate('SongsPlugin.OpenLP1SongImport', 'The file you '
|
self.log_error(self.import_source,
|
||||||
|
translate('SongsPlugin.OpenLP1SongImport', 'The file you '
|
||||||
'were trying to import is not a valid openlp.org 1.x song '
|
'were trying to import is not a valid openlp.org 1.x song '
|
||||||
'database.')
|
'database.'))
|
||||||
|
return
|
||||||
encoding = self.get_encoding()
|
encoding = self.get_encoding()
|
||||||
if not encoding:
|
if not encoding:
|
||||||
return False
|
self.log_error(self.import_source,
|
||||||
|
translate('SongsPlugin.OpenLP1SongImport', 'The file you '
|
||||||
|
'were trying to import is not a valid openlp.org 1.x song '
|
||||||
|
'database.'))
|
||||||
|
return
|
||||||
# Connect to the database
|
# Connect to the database
|
||||||
connection = sqlite.connect(self.import_source, mode=0444,
|
connection = sqlite.connect(self.import_source, mode=0444,
|
||||||
encoding=(encoding, 'replace'))
|
encoding=(encoding, 'replace'))
|
||||||
@ -81,7 +87,6 @@ class OpenLP1SongImport(SongImport):
|
|||||||
cursor.execute(u'-- types int')
|
cursor.execute(u'-- types int')
|
||||||
cursor.execute(u'SELECT COUNT(songid) FROM songs')
|
cursor.execute(u'SELECT COUNT(songid) FROM songs')
|
||||||
count = cursor.fetchone()[0]
|
count = cursor.fetchone()[0]
|
||||||
success = True
|
|
||||||
self.import_wizard.progressBar.setMaximum(count)
|
self.import_wizard.progressBar.setMaximum(count)
|
||||||
# "cache" our list of authors
|
# "cache" our list of authors
|
||||||
cursor.execute(u'-- types int, unicode')
|
cursor.execute(u'-- types int, unicode')
|
||||||
@ -100,7 +105,6 @@ class OpenLP1SongImport(SongImport):
|
|||||||
for song in songs:
|
for song in songs:
|
||||||
self.set_defaults()
|
self.set_defaults()
|
||||||
if self.stop_import_flag:
|
if self.stop_import_flag:
|
||||||
success = False
|
|
||||||
break
|
break
|
||||||
song_id = song[0]
|
song_id = song[0]
|
||||||
title = song[1]
|
title = song[1]
|
||||||
@ -110,9 +114,7 @@ class OpenLP1SongImport(SongImport):
|
|||||||
WizardStrings.ImportingType % title)
|
WizardStrings.ImportingType % title)
|
||||||
self.title = title
|
self.title = title
|
||||||
verses = lyrics.split(u'\n\n')
|
verses = lyrics.split(u'\n\n')
|
||||||
for verse in verses:
|
[self.add_verse(verse.strip()) for verse in verses if verse.strip()]
|
||||||
if verse.strip() != u'':
|
|
||||||
self.add_verse(verse.strip())
|
|
||||||
self.add_copyright(copyright)
|
self.add_copyright(copyright)
|
||||||
cursor.execute(u'-- types int')
|
cursor.execute(u'-- types int')
|
||||||
cursor.execute(u'SELECT authorid FROM songauthors '
|
cursor.execute(u'SELECT authorid FROM songauthors '
|
||||||
@ -120,14 +122,12 @@ class OpenLP1SongImport(SongImport):
|
|||||||
author_ids = cursor.fetchall()
|
author_ids = cursor.fetchall()
|
||||||
for author_id in author_ids:
|
for author_id in author_ids:
|
||||||
if self.stop_import_flag:
|
if self.stop_import_flag:
|
||||||
success = False
|
|
||||||
break
|
break
|
||||||
for author in authors:
|
for author in authors:
|
||||||
if author[0] == author_id[0]:
|
if author[0] == author_id[0]:
|
||||||
self.parse_author(author[1])
|
self.parse_author(author[1])
|
||||||
break
|
break
|
||||||
if self.stop_import_flag:
|
if self.stop_import_flag:
|
||||||
success = False
|
|
||||||
break
|
break
|
||||||
if new_db:
|
if new_db:
|
||||||
cursor.execute(u'-- types int')
|
cursor.execute(u'-- types int')
|
||||||
@ -136,17 +136,14 @@ class OpenLP1SongImport(SongImport):
|
|||||||
track_ids = cursor.fetchall()
|
track_ids = cursor.fetchall()
|
||||||
for track_id in track_ids:
|
for track_id in track_ids:
|
||||||
if self.stop_import_flag:
|
if self.stop_import_flag:
|
||||||
success = False
|
|
||||||
break
|
break
|
||||||
for track in tracks:
|
for track in tracks:
|
||||||
if track[0] == track_id[0]:
|
if track[0] == track_id[0]:
|
||||||
self.add_media_file(track[1])
|
self.add_media_file(track[1])
|
||||||
break
|
break
|
||||||
if self.stop_import_flag:
|
if self.stop_import_flag:
|
||||||
success = False
|
|
||||||
break
|
break
|
||||||
self.finish()
|
self.finish()
|
||||||
return success
|
|
||||||
|
|
||||||
def get_encoding(self):
|
def get_encoding(self):
|
||||||
"""
|
"""
|
||||||
|
@ -101,8 +101,10 @@ class OpenLPSongImport(SongImport):
|
|||||||
Run the import for an OpenLP version 2 song database.
|
Run the import for an OpenLP version 2 song database.
|
||||||
"""
|
"""
|
||||||
if not self.import_source.endswith(u'.sqlite'):
|
if not self.import_source.endswith(u'.sqlite'):
|
||||||
return translate('SongsPlugin.OpenLPSongImport', 'The file you were'
|
self.log_error(self.import_source,
|
||||||
' trying to import is not a valid OpenLP 2.0 song database.')
|
translate('SongsPlugin.OpenLPSongImport', 'The file you were '
|
||||||
|
'trying to import is not a valid OpenLP 2.0 song database.'))
|
||||||
|
return
|
||||||
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)
|
||||||
@ -218,5 +220,5 @@ class OpenLPSongImport(SongImport):
|
|||||||
self.manager.save_object(new_song)
|
self.manager.save_object(new_song)
|
||||||
song_count += 1
|
song_count += 1
|
||||||
if self.stop_import_flag:
|
if self.stop_import_flag:
|
||||||
return False
|
break
|
||||||
engine.dispose()
|
engine.dispose()
|
||||||
|
@ -60,7 +60,7 @@ class OooImport(SongImport):
|
|||||||
self.import_wizard.progressBar.setMaximum(self.import_source)
|
self.import_wizard.progressBar.setMaximum(self.import_source)
|
||||||
for filename in self.import_source:
|
for filename in self.import_source:
|
||||||
if self.stop_import_flag:
|
if self.stop_import_flag:
|
||||||
return False
|
break
|
||||||
filename = unicode(filename)
|
filename = unicode(filename)
|
||||||
if os.path.isfile(filename):
|
if os.path.isfile(filename):
|
||||||
self.open_ooo_file(filename)
|
self.open_ooo_file(filename)
|
||||||
|
@ -59,7 +59,7 @@ class OpenLyricsImport(SongImport):
|
|||||||
parser = etree.XMLParser(remove_blank_text=True)
|
parser = etree.XMLParser(remove_blank_text=True)
|
||||||
for file_path in self.import_source:
|
for file_path in self.import_source:
|
||||||
if self.stop_import_flag:
|
if self.stop_import_flag:
|
||||||
return False
|
return
|
||||||
self.import_wizard.incrementProgressBar(
|
self.import_wizard.incrementProgressBar(
|
||||||
WizardStrings.ImportingType % os.path.basename(file_path))
|
WizardStrings.ImportingType % os.path.basename(file_path))
|
||||||
try:
|
try:
|
||||||
|
@ -26,12 +26,14 @@
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
from zipfile import ZipFile
|
from zipfile import ZipFile
|
||||||
|
|
||||||
from lxml import objectify
|
from lxml import objectify
|
||||||
from lxml.etree import Error, LxmlError
|
from lxml.etree import Error, LxmlError
|
||||||
import re
|
|
||||||
|
|
||||||
from openlp.core.ui.wizard import WizardStrings
|
from openlp.core.ui.wizard import WizardStrings
|
||||||
|
from openlp.plugins.songs.lib import VerseType
|
||||||
from openlp.plugins.songs.lib.songimport import SongImport
|
from openlp.plugins.songs.lib.songimport import SongImport
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
@ -105,29 +107,26 @@ class OpenSongImport(SongImport):
|
|||||||
Initialise the class.
|
Initialise the class.
|
||||||
"""
|
"""
|
||||||
SongImport.__init__(self, manager, **kwargs)
|
SongImport.__init__(self, manager, **kwargs)
|
||||||
self.commit = True
|
|
||||||
|
|
||||||
def do_import(self):
|
def do_import(self):
|
||||||
"""
|
"""
|
||||||
Import either each of the files in self.import_source - each element of
|
Import either each of the files in self.import_source - each element of
|
||||||
which can be either a single opensong file, or a zipfile containing
|
which can be either a single opensong file, or a zipfile containing
|
||||||
multiple opensong files. If `self.commit` is set False, the
|
multiple opensong files.
|
||||||
import will not be committed to the database (useful for test scripts).
|
|
||||||
"""
|
"""
|
||||||
success = True
|
|
||||||
numfiles = 0
|
numfiles = 0
|
||||||
for filename in self.import_source:
|
for filename in self.import_source:
|
||||||
ext = os.path.splitext(filename)[1]
|
ext = os.path.splitext(filename)[1]
|
||||||
if ext.lower() == u'.zip':
|
if ext.lower() == u'.zip':
|
||||||
z = ZipFile(filename, u'r')
|
z = ZipFile(filename, u'r')
|
||||||
numfiles += len(z.infolist())
|
numfiles += len(z.infolist())
|
||||||
|
z.close()
|
||||||
else:
|
else:
|
||||||
numfiles += 1
|
numfiles += 1
|
||||||
log.debug(u'Total number of files: %d', numfiles)
|
log.debug(u'Total number of files: %d', numfiles)
|
||||||
self.import_wizard.progressBar.setMaximum(numfiles)
|
self.import_wizard.progressBar.setMaximum(numfiles)
|
||||||
for filename in self.import_source:
|
for filename in self.import_source:
|
||||||
if self.stop_import_flag:
|
if self.stop_import_flag:
|
||||||
success = False
|
|
||||||
break
|
break
|
||||||
ext = os.path.splitext(filename)[1]
|
ext = os.path.splitext(filename)[1]
|
||||||
if ext.lower() == u'.zip':
|
if ext.lower() == u'.zip':
|
||||||
@ -135,48 +134,36 @@ class OpenSongImport(SongImport):
|
|||||||
z = ZipFile(filename, u'r')
|
z = ZipFile(filename, u'r')
|
||||||
for song in z.infolist():
|
for song in z.infolist():
|
||||||
if self.stop_import_flag:
|
if self.stop_import_flag:
|
||||||
success = False
|
|
||||||
break
|
break
|
||||||
parts = os.path.split(song.filename)
|
parts = os.path.split(song.filename)
|
||||||
if parts[-1] == u'':
|
if parts[-1] == u'':
|
||||||
#No final part => directory
|
# No final part => directory
|
||||||
continue
|
continue
|
||||||
log.info(u'Zip importing %s', parts[-1])
|
log.info(u'Zip importing %s', parts[-1])
|
||||||
self.import_wizard.incrementProgressBar(
|
self.import_wizard.incrementProgressBar(
|
||||||
WizardStrings.ImportingType % parts[-1])
|
WizardStrings.ImportingType % parts[-1])
|
||||||
songfile = z.open(song)
|
song_file = z.open(song)
|
||||||
if self.do_import_file(songfile) and self.commit and \
|
self.do_import_file(song_file)
|
||||||
not self.stop_import_flag:
|
song_file.close()
|
||||||
self.finish()
|
z.close()
|
||||||
else:
|
|
||||||
success = False
|
|
||||||
break
|
|
||||||
else:
|
else:
|
||||||
# not a zipfile
|
# not a zipfile
|
||||||
log.info(u'Direct import %s', filename)
|
log.info(u'Direct import %s', filename)
|
||||||
self.import_wizard.incrementProgressBar(
|
self.import_wizard.incrementProgressBar(
|
||||||
WizardStrings.ImportingType % os.path.split(filename)[-1])
|
WizardStrings.ImportingType % os.path.split(filename)[-1])
|
||||||
song_file = open(filename)
|
song_file = open(filename)
|
||||||
if self.do_import_file(song_file) and self.commit and \
|
self.do_import_file(song_file)
|
||||||
not self.stop_import_flag:
|
song_file.close()
|
||||||
self.finish()
|
|
||||||
else:
|
|
||||||
success = False
|
|
||||||
break
|
|
||||||
if not success:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def do_import_file(self, file):
|
def do_import_file(self, file):
|
||||||
"""
|
"""
|
||||||
Process the OpenSong file - pass in a file-like object,
|
Process the OpenSong file - pass in a file-like object, not a file path.
|
||||||
not a filename
|
|
||||||
"""
|
"""
|
||||||
self.set_defaults()
|
self.set_defaults()
|
||||||
try:
|
try:
|
||||||
tree = objectify.parse(file)
|
tree = objectify.parse(file)
|
||||||
except (Error, LxmlError):
|
except (Error, LxmlError):
|
||||||
log.exception(u'Error parsing XML')
|
log.exception(u'Error parsing XML')
|
||||||
return False
|
|
||||||
root = tree.getroot()
|
root = tree.getroot()
|
||||||
fields = dir(root)
|
fields = dir(root)
|
||||||
decode = {
|
decode = {
|
||||||
@ -196,7 +183,7 @@ class OpenSongImport(SongImport):
|
|||||||
fn_or_string(ustring)
|
fn_or_string(ustring)
|
||||||
if not len(self.title):
|
if not len(self.title):
|
||||||
# to prevent creation of empty songs from wrong files
|
# to prevent creation of empty songs from wrong files
|
||||||
return False
|
return
|
||||||
if u'theme' in fields and unicode(root.theme) not in self.topics:
|
if u'theme' in fields and unicode(root.theme) not in self.topics:
|
||||||
self.topics.append(unicode(root.theme))
|
self.topics.append(unicode(root.theme))
|
||||||
if u'alttheme' in fields and unicode(root.alttheme) not in self.topics:
|
if u'alttheme' in fields and unicode(root.alttheme) not in self.topics:
|
||||||
@ -206,7 +193,7 @@ class OpenSongImport(SongImport):
|
|||||||
# keep track of verses appearance order
|
# keep track of verses appearance order
|
||||||
our_verse_order = []
|
our_verse_order = []
|
||||||
# default verse
|
# default verse
|
||||||
verse_tag = u'v'
|
verse_tag = VerseType.Tags[VerseType.Verse]
|
||||||
verse_num = u'1'
|
verse_num = u'1'
|
||||||
# for the case where song has several sections with same marker
|
# for the case where song has several sections with same marker
|
||||||
inst = 1
|
inst = 1
|
||||||
@ -244,7 +231,7 @@ class OpenSongImport(SongImport):
|
|||||||
if [verse_tag, verse_num, inst] in our_verse_order \
|
if [verse_tag, verse_num, inst] in our_verse_order \
|
||||||
and verses.has_key(verse_tag) \
|
and verses.has_key(verse_tag) \
|
||||||
and verses[verse_tag].has_key(verse_num):
|
and verses[verse_tag].has_key(verse_num):
|
||||||
inst = len(verses[verse_tag][verse_num])+1
|
inst = len(verses[verse_tag][verse_num]) + 1
|
||||||
our_verse_order.append([verse_tag, verse_num, inst])
|
our_verse_order.append([verse_tag, verse_num, inst])
|
||||||
continue
|
continue
|
||||||
# number at start of line.. it's verse number
|
# number at start of line.. it's verse number
|
||||||
@ -293,4 +280,4 @@ class OpenSongImport(SongImport):
|
|||||||
else:
|
else:
|
||||||
log.info(u'Got order %s but not in verse tags, dropping'
|
log.info(u'Got order %s but not in verse tags, dropping'
|
||||||
u'this item from presentation order', verse_def)
|
u'this item from presentation order', verse_def)
|
||||||
return True
|
self.finish()
|
||||||
|
@ -88,8 +88,7 @@ class SofImport(OooImport):
|
|||||||
paragraphs = self.document.getText().createEnumeration()
|
paragraphs = self.document.getText().createEnumeration()
|
||||||
while paragraphs.hasMoreElements():
|
while paragraphs.hasMoreElements():
|
||||||
if self.stop_import_flag:
|
if self.stop_import_flag:
|
||||||
self.import_wizard.incrementProgressBar(u'Import cancelled', 0)
|
return
|
||||||
return False
|
|
||||||
paragraph = paragraphs.nextElement()
|
paragraph = paragraphs.nextElement()
|
||||||
if paragraph.supportsService("com.sun.star.text.Paragraph"):
|
if paragraph.supportsService("com.sun.star.text.Paragraph"):
|
||||||
self.process_paragraph(paragraph)
|
self.process_paragraph(paragraph)
|
||||||
|
@ -33,7 +33,6 @@ import logging
|
|||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from openlp.core.ui.wizard import WizardStrings
|
|
||||||
from openlp.plugins.songs.lib import VerseType
|
from openlp.plugins.songs.lib import VerseType
|
||||||
from openlp.plugins.songs.lib.songimport import SongImport
|
from openlp.plugins.songs.lib.songimport import SongImport
|
||||||
|
|
||||||
@ -80,18 +79,16 @@ class SongBeamerImport(SongImport):
|
|||||||
"""
|
"""
|
||||||
self.import_wizard.progressBar.setMaximum(len(self.import_source))
|
self.import_wizard.progressBar.setMaximum(len(self.import_source))
|
||||||
if not isinstance(self.import_source, list):
|
if not isinstance(self.import_source, list):
|
||||||
return False
|
return
|
||||||
for file in self.import_source:
|
for file in self.import_source:
|
||||||
# TODO: check that it is a valid SongBeamer file
|
# TODO: check that it is a valid SongBeamer file
|
||||||
if self.stop_import_flag:
|
if self.stop_import_flag:
|
||||||
return False
|
return
|
||||||
self.set_defaults()
|
self.set_defaults()
|
||||||
self.current_verse = u''
|
self.current_verse = u''
|
||||||
self.current_verse_type = VerseType.Tags[VerseType.Verse]
|
self.current_verse_type = VerseType.Tags[VerseType.Verse]
|
||||||
read_verses = False
|
read_verses = False
|
||||||
file_name = os.path.split(file)[1]
|
file_name = os.path.split(file)[1]
|
||||||
self.import_wizard.incrementProgressBar(
|
|
||||||
WizardStrings.ImportingType % file_name, 0)
|
|
||||||
if os.path.isfile(file):
|
if os.path.isfile(file):
|
||||||
detect_file = open(file, u'r')
|
detect_file = open(file, u'r')
|
||||||
details = chardet.detect(detect_file.read(2048))
|
details = chardet.detect(detect_file.read(2048))
|
||||||
@ -100,7 +97,7 @@ class SongBeamerImport(SongImport):
|
|||||||
songData = infile.readlines()
|
songData = infile.readlines()
|
||||||
infile.close()
|
infile.close()
|
||||||
else:
|
else:
|
||||||
return False
|
continue
|
||||||
self.title = file_name.split('.sng')[0]
|
self.title = file_name.split('.sng')[0]
|
||||||
read_verses = False
|
read_verses = False
|
||||||
for line in songData:
|
for line in songData:
|
||||||
@ -127,10 +124,7 @@ class SongBeamerImport(SongImport):
|
|||||||
if self.current_verse:
|
if self.current_verse:
|
||||||
self.replace_html_tags()
|
self.replace_html_tags()
|
||||||
self.add_verse(self.current_verse, self.current_verse_type)
|
self.add_verse(self.current_verse, self.current_verse_type)
|
||||||
if self.check_complete():
|
|
||||||
self.finish()
|
self.finish()
|
||||||
self.import_wizard.incrementProgressBar(
|
|
||||||
WizardStrings.ImportingType % file_name)
|
|
||||||
|
|
||||||
def replace_html_tags(self):
|
def replace_html_tags(self):
|
||||||
"""
|
"""
|
||||||
@ -288,5 +282,4 @@ class SongBeamerImport(SongImport):
|
|||||||
if marks[1].isdigit():
|
if marks[1].isdigit():
|
||||||
self.current_verse_type += marks[1]
|
self.current_verse_type += marks[1]
|
||||||
return True
|
return True
|
||||||
else:
|
|
||||||
return False
|
return False
|
||||||
|
@ -29,6 +29,7 @@ import re
|
|||||||
from PyQt4 import QtCore
|
from PyQt4 import QtCore
|
||||||
|
|
||||||
from openlp.core.lib import Receiver, translate
|
from openlp.core.lib import Receiver, translate
|
||||||
|
from openlp.core.ui.wizard import WizardStrings
|
||||||
from openlp.plugins.songs.lib import clean_song, VerseType
|
from openlp.plugins.songs.lib import clean_song, VerseType
|
||||||
from openlp.plugins.songs.lib.db import Song, Author, Topic, Book, MediaFile
|
from openlp.plugins.songs.lib.db import Song, Author, Topic, Book, MediaFile
|
||||||
from openlp.plugins.songs.lib.ui import SongStrings
|
from openlp.plugins.songs.lib.ui import SongStrings
|
||||||
@ -66,6 +67,7 @@ class SongImport(QtCore.QObject):
|
|||||||
self.song = None
|
self.song = None
|
||||||
self.stop_import_flag = False
|
self.stop_import_flag = False
|
||||||
self.set_defaults()
|
self.set_defaults()
|
||||||
|
self.import_error_log = []
|
||||||
QtCore.QObject.connect(Receiver.get_receiver(),
|
QtCore.QObject.connect(Receiver.get_receiver(),
|
||||||
QtCore.SIGNAL(u'openlp_stop_wizard'), self.stop_import)
|
QtCore.SIGNAL(u'openlp_stop_wizard'), self.stop_import)
|
||||||
|
|
||||||
@ -94,6 +96,21 @@ class SongImport(QtCore.QObject):
|
|||||||
self.copyright_string = unicode(translate(
|
self.copyright_string = unicode(translate(
|
||||||
'SongsPlugin.SongImport', 'copyright'))
|
'SongsPlugin.SongImport', 'copyright'))
|
||||||
|
|
||||||
|
def log_error(self, filepath, reason=None):
|
||||||
|
"""
|
||||||
|
This should be called, when a song could not be imported.
|
||||||
|
|
||||||
|
``filepath``
|
||||||
|
This should be the file path if ``self.import_source`` is a list
|
||||||
|
with different files. If it is not a list, but a single file (for
|
||||||
|
instance a database), then this should be the song's title.
|
||||||
|
|
||||||
|
``reason``
|
||||||
|
The reason, why the import failed. The string should be as
|
||||||
|
informative as possible.
|
||||||
|
"""
|
||||||
|
self.import_error_log.append((filepath, unicode(reason)))
|
||||||
|
|
||||||
def stop_import(self):
|
def stop_import(self):
|
||||||
"""
|
"""
|
||||||
Sets the flag for importers to stop their import
|
Sets the flag for importers to stop their import
|
||||||
@ -249,9 +266,15 @@ class SongImport(QtCore.QObject):
|
|||||||
"""
|
"""
|
||||||
All fields have been set to this song. Write the song to disk.
|
All fields have been set to this song. Write the song to disk.
|
||||||
"""
|
"""
|
||||||
|
if not self.check_complete():
|
||||||
|
self.set_defaults()
|
||||||
|
return
|
||||||
log.info(u'committing song %s to database', self.title)
|
log.info(u'committing song %s to database', self.title)
|
||||||
song = Song()
|
song = Song()
|
||||||
song.title = self.title
|
song.title = self.title
|
||||||
|
self.import_wizard.incrementProgressBar(
|
||||||
|
WizardStrings.ImportingType % song.title)
|
||||||
|
print WizardStrings.ImportingType
|
||||||
song.alternate_title = self.alternate_title
|
song.alternate_title = self.alternate_title
|
||||||
# Values will be set when cleaning the song.
|
# Values will be set when cleaning the song.
|
||||||
song.search_title = u''
|
song.search_title = u''
|
||||||
|
@ -32,6 +32,7 @@ import logging
|
|||||||
import struct
|
import struct
|
||||||
|
|
||||||
from openlp.core.ui.wizard import WizardStrings
|
from openlp.core.ui.wizard import WizardStrings
|
||||||
|
from openlp.plugins.songs.lib import VerseType
|
||||||
from openlp.plugins.songs.lib.songimport import SongImport
|
from openlp.plugins.songs.lib.songimport import SongImport
|
||||||
|
|
||||||
TITLE = 1
|
TITLE = 1
|
||||||
@ -97,6 +98,7 @@ class SongShowPlusImport(SongImport):
|
|||||||
Receive a single file or a list of files to import.
|
Receive a single file or a list of files to import.
|
||||||
"""
|
"""
|
||||||
if isinstance(self.import_source, list):
|
if isinstance(self.import_source, list):
|
||||||
|
return
|
||||||
self.import_wizard.progressBar.setMaximum(len(self.import_source))
|
self.import_wizard.progressBar.setMaximum(len(self.import_source))
|
||||||
for file in self.import_source:
|
for file in self.import_source:
|
||||||
author = u''
|
author = u''
|
||||||
|
Loading…
Reference in New Issue
Block a user