Merge lp:~sfindlay/openlp/zionworx-import into lp:openlp

Proposed by Samuel Findlay
Status: Merged
Merged at revision: 1986
Proposed branch: lp:~sfindlay/openlp/zionworx-import
Merge into: lp:openlp
Prerequisite: lp:~sfindlay/openlp/refactor-song-import
Diff against target: 342 lines (+202/-14)
5 files modified
openlp/core/ui/wizard.py (+1/-1)
openlp/plugins/bibles/lib/csvbible.py (+1/-1)
openlp/plugins/songs/forms/songimportform.py (+37/-9)
openlp/plugins/songs/lib/importer.py (+21/-3)
openlp/plugins/songs/lib/zionworximport.py (+142/-0)
To merge this branch: bzr merge lp:~sfindlay/openlp/zionworx-import
Reviewer Review Type Date Requested Status
Jonathan Corwin (community) Approve
Raoul Snyman Approve
Phill Pending
Review via email: mp+109433@code.launchpad.net

This proposal supersedes a proposal from 2012-06-07.

Commit message

Added ZionWorx importer. Added descriptionLabel to song import wizard.

Description of the change

Added ZionWorx song database importer.
* Tested on win7 x64 with databases from 3 users. Total 3171 songs, including non-English characters.
* ZionWorx [1] is freeware and windows-only
* Users can download freeware utility "TurboDB Data Exchange" [2] (Win/Linux) and use this command to dump their ZionWorx database to a CSV file which can then be imported by OpenLP:
>> tdbdatax MainTable.dat songstable.csv -fsdf -s, -qd
* Since the importer is not a direct ZionWorx import, I added a descriptionLabel widget to the import wizard, pointing users to the Manual for further info.
* This descriptionLabel widget is also available to other importers (see bug 832345) [3]

[1] http://www.zionworx.org.uk/
[2] http://www.dataweb.de/en/support/downloads.html
[3] https://bugs.launchpad.net/openlp/+bug/832345

To post a comment you must log in.
Revision history for this message
Jonathan Corwin (j-corwin) wrote : Posted in a previous version of this proposal

189-191: Would it be possible to translate the paragraph into English? :) Remember the users may not be technical so lots of "dump", "CSV" type phrases might scare folk off.

review: Needs Fixing
Revision history for this message
Raoul Snyman (raoul-snyman) :
review: Approve
Revision history for this message
Jonathan Corwin (j-corwin) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'openlp/core/ui/wizard.py'
2--- openlp/core/ui/wizard.py 2012-06-08 21:10:26 +0000
3+++ openlp/core/ui/wizard.py 2012-06-08 21:10:26 +0000
4@@ -99,7 +99,7 @@
5
6 def setupUi(self, image):
7 """
8- Set up the wizard UI
9+ Set up the wizard UI.
10 """
11 self.setModal(True)
12 self.setWizardStyle(QtGui.QWizard.ModernStyle)
13
14=== modified file 'openlp/plugins/bibles/lib/csvbible.py'
15--- openlp/plugins/bibles/lib/csvbible.py 2011-12-27 10:33:55 +0000
16+++ openlp/plugins/bibles/lib/csvbible.py 2012-06-08 21:10:26 +0000
17@@ -73,7 +73,7 @@
18
19 def __init__(self, parent, **kwargs):
20 """
21- Loads a Bible from a set of CVS files.
22+ Loads a Bible from a set of CSV files.
23 This class assumes the files contain all the information and
24 a clean bible is being loaded.
25 """
26
27=== modified file 'openlp/plugins/songs/forms/songimportform.py'
28--- openlp/plugins/songs/forms/songimportform.py 2012-06-08 21:10:26 +0000
29+++ openlp/plugins/songs/forms/songimportform.py 2012-06-08 21:10:26 +0000
30@@ -133,6 +133,9 @@
31 self.formatLayout.setItem(1, QtGui.QFormLayout.LabelRole,
32 self.formatSpacer)
33 self.sourceLayout.addLayout(self.formatLayout)
34+ self.formatHSpacing = self.formatLayout.horizontalSpacing()
35+ self.formatVSpacing = self.formatLayout.verticalSpacing()
36+ self.formatLayout.setVerticalSpacing(0)
37 self.stackSpacer = QtGui.QSpacerItem(10, 0, QtGui.QSizePolicy.Fixed,
38 QtGui.QSizePolicy.Expanding)
39 self.formatStack = QtGui.QStackedLayout()
40@@ -160,11 +163,15 @@
41 self.sourcePage.setSubTitle(WizardStrings.ImportSelectLong)
42 self.formatLabel.setText(WizardStrings.FormatLabel)
43 for format in SongFormat.get_format_list():
44- format_name, custom_combo_text, select_mode = SongFormat.get(
45- format, u'name', u'comboBoxText', u'selectMode')
46- combo_box_text = custom_combo_text if custom_combo_text \
47- else format_name
48+ format_name, custom_combo_text, description_text, select_mode = \
49+ SongFormat.get(format, u'name', u'comboBoxText',
50+ u'descriptionText', u'selectMode')
51+ combo_box_text = (custom_combo_text if custom_combo_text else
52+ format_name)
53 self.formatComboBox.setItemText(format, combo_box_text)
54+ if description_text is not None:
55+ self.formatWidgets[format][u'descriptionLabel'].setText(
56+ description_text)
57 if select_mode == SongFormatSelect.MultipleFiles:
58 self.formatWidgets[format][u'addButton'].setText(
59 translate('SongsPlugin.ImportWizardForm', 'Add Files...'))
60@@ -205,10 +212,16 @@
61 spacer.changeSize(
62 max_label_width - labels[index].minimumSizeHint().width(), 0,
63 QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)
64+ # Align descriptionLabels with rest of layout
65+ for format in SongFormat.get_format_list():
66+ if SongFormat.get(format, u'descriptionText') is not None:
67+ self.formatWidgets[format][u'descriptionSpacer'].changeSize(
68+ max_label_width + self.formatHSpacing, 0,
69+ QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)
70
71 def customPageChanged(self, pageId):
72 """
73- Called when changing to a page other than the progress page
74+ Called when changing to a page other than the progress page.
75 """
76 if self.page(pageId) == self.sourcePage:
77 self.onCurrentIndexChanged(self.formatStack.currentIndex())
78@@ -235,8 +248,8 @@
79 else:
80 import_source = \
81 self.formatWidgets[format][u'filepathEdit'].text()
82- error_title = UiStrings().IFSs if select_mode == \
83- SongFormatSelect.SingleFile else UiStrings().IFdSs
84+ error_title = (UiStrings().IFSs if select_mode ==
85+ SongFormatSelect.SingleFile else UiStrings().IFdSs)
86 focus_button = self.formatWidgets[format][u'browseButton']
87 if not class_.isValidSource(import_source):
88 critical_error_message_box(error_title, error_msg)
89@@ -395,8 +408,8 @@
90
91 def addFileSelectItem(self):
92 format = self.currentFormat
93- prefix, can_disable, select_mode = SongFormat.get(format, u'prefix',
94- u'canDisable', u'selectMode')
95+ prefix, can_disable, description_text, select_mode = SongFormat.get(
96+ format, u'prefix', u'canDisable', u'descriptionText', u'selectMode')
97 page = QtGui.QWidget()
98 page.setObjectName(prefix + u'Page')
99 if can_disable:
100@@ -406,10 +419,25 @@
101 importLayout = QtGui.QVBoxLayout(importWidget)
102 importLayout.setMargin(0)
103 importLayout.setObjectName(prefix + u'ImportLayout')
104+ if description_text is not None:
105+ descriptionLayout = QtGui.QHBoxLayout()
106+ descriptionLayout.setObjectName(prefix + u'DescriptionLayout')
107+ descriptionSpacer = QtGui.QSpacerItem(0, 0, QtGui.QSizePolicy.Fixed,
108+ QtGui.QSizePolicy.Fixed)
109+ descriptionLayout.addSpacerItem(descriptionSpacer)
110+ descriptionLabel = QtGui.QLabel(importWidget)
111+ descriptionLabel.setWordWrap(True)
112+ descriptionLabel.setOpenExternalLinks(True)
113+ descriptionLabel.setObjectName(prefix + u'DescriptionLabel')
114+ descriptionLayout.addWidget(descriptionLabel)
115+ importLayout.addLayout(descriptionLayout)
116+ self.formatWidgets[format][u'descriptionLabel'] = descriptionLabel
117+ self.formatWidgets[format][u'descriptionSpacer'] = descriptionSpacer
118 if select_mode == SongFormatSelect.SingleFile or \
119 select_mode == SongFormatSelect.SingleFolder:
120 filepathLayout = QtGui.QHBoxLayout()
121 filepathLayout.setObjectName(prefix + u'FilepathLayout')
122+ filepathLayout.setContentsMargins(0, self.formatVSpacing, 0, 0)
123 filepathLabel = QtGui.QLabel(importWidget)
124 filepathLabel.setObjectName(prefix + u'FilepathLabel')
125 filepathLayout.addWidget(filepathLabel)
126
127=== modified file 'openlp/plugins/songs/lib/importer.py'
128--- openlp/plugins/songs/lib/importer.py 2012-06-08 21:10:26 +0000
129+++ openlp/plugins/songs/lib/importer.py 2012-06-08 21:10:26 +0000
130@@ -44,6 +44,7 @@
131 from songbeamerimport import SongBeamerImport
132 from songshowplusimport import SongShowPlusImport
133 from foilpresenterimport import FoilPresenterImport
134+from zionworximport import ZionWorxImport
135 # Imports that might fail
136 log = logging.getLogger(__name__)
137 try:
138@@ -111,6 +112,8 @@
139 Title for ``QFileDialog`` (default includes the format's ``u'name'``).
140 ``u'invalidSourceMsg'``
141 Message displayed if ``isValidSource()`` returns ``False``.
142+ ``u'descriptionText'``
143+ Short description (1-2 lines) about the song format.
144 """
145 # Song formats (ordered alphabetically after Generic)
146 # * Numerical order of song formats is significant as it determines the
147@@ -131,7 +134,8 @@
148 SongShowPlus = 12
149 SongsOfFellowship = 13
150 WordsOfWorship = 14
151- #CSV = 15
152+ ZionWorx = 15
153+ #CSV = 16
154
155 # Set optional attribute defaults
156 __defaults__ = {
157@@ -142,7 +146,8 @@
158 u'comboBoxText': None,
159 u'disabledLabelText': u'',
160 u'getFilesTitle': None,
161- u'invalidSourceMsg': None
162+ u'invalidSourceMsg': None,
163+ u'descriptionText': None
164 }
165
166 # Set attribute values for each Song Format
167@@ -264,6 +269,18 @@
168 u'prefix': u'wordsOfWorship',
169 u'filter': u'%s (*.wsg *.wow-song)' % translate(
170 'SongsPlugin.ImportWizardForm', 'Words Of Worship Song Files')
171+ },
172+ ZionWorx: {
173+ u'class': ZionWorxImport,
174+ u'name': u'ZionWorx',
175+ u'prefix': u'zionWorx',
176+ u'selectMode': SongFormatSelect.SingleFile,
177+ u'comboBoxText': translate('SongsPlugin.ImportWizardForm',
178+ 'ZionWorx (CSV)'),
179+ u'descriptionText': translate('SongsPlugin.ImportWizardForm',
180+ 'First convert your ZionWorx database to a CSV text file, as '
181+ 'explained in the <a href="http://manual.openlp.org/songs.html'
182+ '#importing-from-zionworx">User Manual</a>.')
183 # },
184 # CSV: {
185 # u'class': CSVImport,
186@@ -293,7 +310,8 @@
187 SongFormat.SongBeamer,
188 SongFormat.SongShowPlus,
189 SongFormat.SongsOfFellowship,
190- SongFormat.WordsOfWorship
191+ SongFormat.WordsOfWorship,
192+ SongFormat.ZionWorx
193 ]
194
195 @staticmethod
196
197=== added file 'openlp/plugins/songs/lib/zionworximport.py'
198--- openlp/plugins/songs/lib/zionworximport.py 1970-01-01 00:00:00 +0000
199+++ openlp/plugins/songs/lib/zionworximport.py 2012-06-08 21:10:26 +0000
200@@ -0,0 +1,142 @@
201+# -*- coding: utf-8 -*-
202+# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4
203+
204+###############################################################################
205+# OpenLP - Open Source Lyrics Projection #
206+# --------------------------------------------------------------------------- #
207+# Copyright (c) 2008-2012 Raoul Snyman #
208+# Portions copyright (c) 2008-2012 Tim Bentley, Gerald Britton, Jonathan #
209+# Corwin, Michael Gorven, Scott Guerrieri, Matthias Hub, Meinert Jordan, #
210+# Armin Köhler, Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias #
211+# Põldaru, Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
212+# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Frode Woldsund #
213+# --------------------------------------------------------------------------- #
214+# This program is free software; you can redistribute it and/or modify it #
215+# under the terms of the GNU General Public License as published by the Free #
216+# Software Foundation; version 2 of the License. #
217+# #
218+# This program is distributed in the hope that it will be useful, but WITHOUT #
219+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
220+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
221+# more details. #
222+# #
223+# You should have received a copy of the GNU General Public License along #
224+# with this program; if not, write to the Free Software Foundation, Inc., 59 #
225+# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
226+###############################################################################
227+"""
228+The :mod:`zionworximport` module provides the functionality for importing
229+ZionWorx songs into the OpenLP database.
230+"""
231+import csv
232+import logging
233+
234+from openlp.core.lib import translate
235+from openlp.plugins.songs.lib.songimport import SongImport
236+
237+log = logging.getLogger(__name__)
238+
239+class ZionWorxImport(SongImport):
240+ """
241+ The :class:`ZionWorxImport` class provides the ability to import songs
242+ from ZionWorx, via a dump of the ZionWorx database to a CSV file.
243+
244+ ZionWorx song database fields:
245+
246+ * ``SongNum`` Song ID. (Discarded by importer)
247+ * ``Title1`` Main Title.
248+ * ``Title2`` Alternate Title.
249+ * ``Lyrics`` Song verses, separated by blank lines.
250+ * ``Writer`` Song author(s).
251+ * ``Copyright`` Copyright information
252+ * ``Keywords`` (Discarded by importer)
253+ * ``DefaultStyle`` (Discarded by importer)
254+
255+ ZionWorx has no native export function; it uses the proprietary TurboDB
256+ database engine. The TurboDB vendor, dataWeb, provides tools which can
257+ export TurboDB tables to other formats, such as freeware console tool
258+ TurboDB Data Exchange which is available for Windows and Linux. This command
259+ exports the ZionWorx songs table to a CSV file:
260+
261+ ``tdbdatax MainTable.dat songstable.csv -fsdf -s, -qd``
262+
263+ * -f Table format: ``sdf`` denotes text file.
264+ * -s Separator character between fields.
265+ * -q Quote character surrounding fields. ``d`` denotes double-quote.
266+
267+ CSV format expected by importer:
268+
269+ * Field separator character is comma ``,``
270+ * Fields surrounded by double-quotes ``"``. This enables fields (such as
271+ Lyrics) to include new-lines and commas. Double-quotes within a field
272+ are denoted by two double-quotes ``""``
273+ * Note: This is the default format of the Python ``csv`` module.
274+
275+ """
276+ def doImport(self):
277+ """
278+ Receive a CSV file (from a ZionWorx database dump) to import.
279+ """
280+ # Used to strip control chars (10=LF, 13=CR, 127=DEL)
281+ self.control_chars_map = dict.fromkeys(
282+ range(10) + [11, 12] + range(14,32) + [127])
283+ with open(self.importSource, 'rb') as songs_file:
284+ fieldnames = [u'SongNum', u'Title1', u'Title2', u'Lyrics',
285+ u'Writer', u'Copyright', u'Keywords', u'DefaultStyle']
286+ songs_reader = csv.DictReader(songs_file, fieldnames)
287+ try:
288+ records = list(songs_reader)
289+ except csv.Error, e:
290+ self.logError(unicode(translate('SongsPlugin.ZionWorxImport',
291+ 'Error reading CSV file.')),
292+ unicode(translate('SongsPlugin.ZionWorxImport',
293+ 'Line %d: %s' % (songs_reader.line_num, e))))
294+ return
295+ num_records = len(records)
296+ log.info(u'%s records found in CSV file' % num_records)
297+ self.importWizard.progressBar.setMaximum(num_records)
298+ for index, record in enumerate(records, 1):
299+ if self.stopImportFlag:
300+ return
301+ self.setDefaults()
302+ try:
303+ self.title = self._decode(record[u'Title1'])
304+ if record[u'Title2']:
305+ self.alternateTitle = self._decode(record[u'Title2'])
306+ self.parseAuthor(self._decode(record[u'Writer']))
307+ self.addCopyright(self._decode(record[u'Copyright']))
308+ lyrics = self._decode(record[u'Lyrics'])
309+ except UnicodeDecodeError, e:
310+ self.logError(unicode(translate(
311+ 'SongsPlugin.ZionWorxImport', 'Record %d' % index)),
312+ unicode(translate('SongsPlugin.ZionWorxImport',
313+ 'Decoding error: %s' % e)))
314+ continue
315+ except TypeError, e:
316+ self.logError(unicode(translate(
317+ 'SongsPlugin.ZionWorxImport', 'File not valid ZionWorx '
318+ 'CSV format.')), u'TypeError: %s' % e)
319+ return
320+ verse = u''
321+ for line in lyrics.splitlines():
322+ if line and not line.isspace():
323+ verse += line + u'\n'
324+ elif verse:
325+ self.addVerse(verse)
326+ verse = u''
327+ if verse:
328+ self.addVerse(verse)
329+ title = self.title
330+ if not self.finish():
331+ self.logError(unicode(translate(
332+ 'SongsPlugin.ZionWorxImport', 'Record %d' % index))
333+ + (u': "' + title + u'"' if title else u''))
334+
335+ def _decode(self, str):
336+ """
337+ Decodes CSV input to unicode, stripping all control characters (except
338+ new lines).
339+ """
340+ # This encoding choice seems OK. ZionWorx has no option for setting the
341+ # encoding for its songs, so we assume encoding is always the same.
342+ return unicode(str, u'cp1252').translate(self.control_chars_map)