← Back to team overview

openlp-core team mailing list archive

[Merge] lp:~whydoubt/openlp/easyworship into lp:openlp

 

Jeffrey Smith has proposed merging lp:~whydoubt/openlp/easyworship into lp:openlp.

Requested reviews:
  Tim Bentley (trb143)


Adds an importer for EasyWorship song databases.
Adds an entry in the song import wizard for EasyWorship.
-- 
https://code.launchpad.net/~whydoubt/openlp/easyworship/+merge/36074
Your team OpenLP Core is subscribed to branch lp:openlp.
=== modified file 'openlp/plugins/songs/forms/songimportform.py'
--- openlp/plugins/songs/forms/songimportform.py	2010-09-13 21:09:13 +0000
+++ openlp/plugins/songs/forms/songimportform.py	2010-09-20 21:11:03 +0000
@@ -109,6 +109,9 @@
         QtCore.QObject.connect(self.genericRemoveButton,
             QtCore.SIGNAL(u'clicked()'),
             self.onGenericRemoveButtonClicked)
+        QtCore.QObject.connect(self.ewBrowseButton,
+            QtCore.SIGNAL(u'clicked()'),
+            self.onEWBrowseButtonClicked)
         QtCore.QObject.connect(self.cancelButton,
             QtCore.SIGNAL(u'clicked(bool)'),
             self.onCancelButtonClicked)
@@ -214,6 +217,16 @@
                             'presentation file to import from.'))
                     self.genericAddButton.setFocus()
                     return False
+            elif source_format == SongFormat.EasyWorship:
+                if self.ewFilenameEdit.text().isEmpty():
+                    QtGui.QMessageBox.critical(self,
+                        translate('SongsPlugin.ImportWizardForm',
+                            'No EasyWorship Song Database Selected'),
+                        translate('SongsPlugin.ImportWizardForm',
+                            'You need to select an EasyWorship song database '
+                            'file to import from.'))
+                    self.ewBrowseButton.setFocus()
+                    return False
             return True
         elif self.currentId() == 2:
             # Progress page
@@ -322,6 +335,13 @@
     def onGenericRemoveButtonClicked(self):
         self.removeSelectedItems(self.genericFileListWidget)
 
+    def onEWBrowseButtonClicked(self):
+        self.getFileName(
+            translate('SongsPlugin.ImportWizardForm',
+            'Select EasyWorship Database File'),
+            self.ewFilenameEdit
+        )
+
     def onCancelButtonClicked(self, checked):
         """
         Stop the import on pressing the cancel button.
@@ -350,6 +370,7 @@
         self.ccliFileListWidget.clear()
         self.songsOfFellowshipFileListWidget.clear()
         self.genericFileListWidget.clear()
+        self.ewFilenameEdit.setText(u'')
         #self.csvFilenameEdit.setText(u'')
 
     def incrementProgressBar(self, status_text, increment=1):
@@ -420,6 +441,11 @@
             importer = self.plugin.importSongs(SongFormat.Generic,
                 filenames=self.getListOfFiles(self.genericFileListWidget)
             )
+        elif source_format == SongFormat.EasyWorship:
+            # Import an OpenLP 2.0 database
+            importer = self.plugin.importSongs(SongFormat.EasyWorship,
+                filename=unicode(self.ewFilenameEdit.text())
+            )
         success = importer.do_import()
         if success:
             # reload songs

=== modified file 'openlp/plugins/songs/forms/songimportwizard.py'
--- openlp/plugins/songs/forms/songimportwizard.py	2010-09-17 20:06:41 +0000
+++ openlp/plugins/songs/forms/songimportwizard.py	2010-09-20 21:11:03 +0000
@@ -96,6 +96,7 @@
         self.formatComboBox.addItem(u'')
         self.formatComboBox.addItem(u'')
         self.formatComboBox.addItem(u'')
+        self.formatComboBox.addItem(u'')
 #        self.formatComboBox.addItem(u'')
         self.formatLayout.addWidget(self.formatComboBox)
         self.formatSpacer = QtGui.QSpacerItem(40, 20,
@@ -413,6 +414,30 @@
         self.genericImportLayout.addLayout(self.genericButtonLayout)
         self.genericLayout.addWidget(self.genericImportWidget)
         self.formatStackedWidget.addWidget(self.genericPage)
+        # EasyWorship
+        self.ewPage = QtGui.QWidget()
+        self.ewPage.setObjectName(u'ewPage')
+        self.ewLayout = QtGui.QFormLayout(self.ewPage)
+        self.ewLayout.setMargin(0)
+        self.ewLayout.setSpacing(8)
+        self.ewLayout.setObjectName(u'ewLayout')
+        self.ewFilenameLabel = QtGui.QLabel(self.ewPage)
+        self.ewFilenameLabel.setObjectName(u'ewFilenameLabel')
+        self.ewLayout.setWidget(0, QtGui.QFormLayout.LabelRole,
+            self.ewFilenameLabel)
+        self.ewFileLayout = QtGui.QHBoxLayout()
+        self.ewFileLayout.setSpacing(8)
+        self.ewFileLayout.setObjectName(u'ewFileLayout')
+        self.ewFilenameEdit = QtGui.QLineEdit(self.ewPage)
+        self.ewFilenameEdit.setObjectName(u'ewFilenameEdit')
+        self.ewFileLayout.addWidget(self.ewFilenameEdit)
+        self.ewBrowseButton = QtGui.QToolButton(self.ewPage)
+        self.ewBrowseButton.setIcon(openIcon)
+        self.ewBrowseButton.setObjectName(u'ewBrowseButton')
+        self.ewFileLayout.addWidget(self.ewBrowseButton)
+        self.ewLayout.setLayout(0, QtGui.QFormLayout.FieldRole,
+            self.ewFileLayout)
+        self.formatStackedWidget.addWidget(self.ewPage)
 #        Commented out for future use.
 #        self.csvPage = QtGui.QWidget()
 #        self.csvPage.setObjectName(u'CSVPage')
@@ -497,7 +522,9 @@
         self.formatComboBox.setItemText(7,
             translate('SongsPlugin.ImportWizardForm',
             'Generic Document/Presentation'))
-#        self.formatComboBox.setItemText(8,
+        self.formatComboBox.setItemText(8,
+            translate('SongsPlugin.ImportWizardForm', 'EasyWorship'))
+#        self.formatComboBox.setItemText(9,
 #            translate('SongsPlugin.ImportWizardForm', 'CSV'))
         self.openLP2FilenameLabel.setText(
             translate('SongsPlugin.ImportWizardForm', 'Filename:'))
@@ -549,6 +576,10 @@
             translate('SongsPlugin.ImportWizardForm', 'The generic document/'
             'presentation importer has been disabled because OpenLP cannot '
             'find OpenOffice.org on your computer.'))
+        self.ewFilenameLabel.setText(
+            translate('SongsPlugin.ImportWizardForm', 'Filename:'))
+        self.ewBrowseButton.setText(
+            translate('SongsPlugin.ImportWizardForm', 'Browse...'))
 #        self.csvFilenameLabel.setText(
 #            translate('SongsPlugin.ImportWizardForm', 'Filename:'))
 #        self.csvBrowseButton.setText(

=== added file 'openlp/plugins/songs/lib/ewimport.py'
--- openlp/plugins/songs/lib/ewimport.py	1970-01-01 00:00:00 +0000
+++ openlp/plugins/songs/lib/ewimport.py	2010-09-20 21:11:03 +0000
@@ -0,0 +1,255 @@
+# -*- coding: utf-8 -*-
+# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4
+
+"""
+The :mod:`ewimport` module provides the functionality for importing
+EasyWorship song databases into the current installation database.
+"""
+
+import sys
+import os
+import struct
+
+from songimport import SongImport
+
+def strip_rtf(blob):
+    depth = 0
+    control = False
+    clear_text = []
+    control_word = []
+    for c in blob:
+        if control:
+            # for delimiters, set control to False 
+            if c == '{':
+                if len(control_word) > 0:
+                    depth += 1
+                control = False
+            elif c == '}':
+                if len(control_word) > 0:
+                    depth -= 1
+                control = False
+            elif c == '\\':
+                new_control = (len(control_word) > 0)
+                control = False
+            elif c.isspace():
+                control = False
+            else:
+                control_word.append(c)
+                if len(control_word) == 3 and control_word[0] == '\'':
+                    control = False
+            if not control:
+                if len(control_word) == 0:
+                    if c == '{' or c == '}' or c == '\\':
+                        clear_text.append(c)
+                else:
+                    control_str = ''.join(control_word)
+                    if control_str == 'par' or control_str == 'line':
+                        clear_text.append(u'\n')
+                    elif control_str == 'tab':
+                        clear_text.append(u'\n')
+                    elif control_str[0] == '\'':
+                        # Really should take RTF character set into account but
+                        # for now assume ANSI (Windows-1252) and call it good
+                        s = chr(int(control_str[1:3], 16))
+                        clear_text.append(s.decode(u'windows-1252'))
+                    del control_word[:]
+            if c == '\\' and new_control:
+                control = True
+        elif c == '{':
+            depth += 1
+        elif c == '}':
+            depth -= 1
+        elif depth > 2:
+            continue
+        elif c == '\n' or c == '\r':
+            continue
+        elif c == '\\':
+            control = True
+        else:
+            clear_text.append(c)
+    return u''.join(clear_text)
+
+class FieldDescEntry:
+    def __init__(self, name, type, size):
+        self.name = name
+        self.type = type
+        self.size = size
+
+class EasyWorshipSongImport(SongImport):
+    """
+    The :class:`EasyWorshipSongImport` class provides OpenLP with the
+    ability to import EasyWorship song files.
+    """
+    def __init__(self, manager, **kwargs):
+        self.import_source = kwargs[u'filename']
+        SongImport.__init__(self, manager)
+
+    def do_import(self):
+        # Open the DB and MB files if they exist
+        import_source_mb = self.import_source.replace('.DB', '.MB')
+        if not os.path.isfile(self.import_source):
+            return False
+        if not os.path.isfile(import_source_mb):
+            return False
+        db_size = os.path.getsize(self.import_source)
+        if db_size < 0x800:
+            return False
+        db_file = open(self.import_source, 'rb')
+        self.memo_file = open(import_source_mb, 'rb')
+        # Don't accept files that are clearly not paradox files
+        record_size, header_size, block_size, first_block, num_fields \
+            = struct.unpack('<hhxb8xh17xh', db_file.read(35))
+        if header_size != 0x800 or block_size < 1 or block_size > 4:
+            db_file.close()
+            self.memo_file.close()
+            return False
+        # There does not appear to be a _reliable_ way of getting the number
+        # of songs/records, so let's use file blocks for measuring progress.
+        total_blocks = (db_size - header_size) / (block_size * 1024)
+        self.import_wizard.importProgressBar.setMaximum(total_blocks)
+        # Read the field description information
+        db_file.seek(120)
+        field_info = db_file.read(num_fields * 2)
+        db_file.seek(4 + (num_fields * 4) + 261, os.SEEK_CUR)
+        field_names = db_file.read(header_size - db_file.tell()).split('\0',
+            num_fields)
+        field_names.pop()
+        field_descs = []
+        for i,field_name in enumerate(field_names):
+            field_type, field_size = struct.unpack_from('BB', field_info, i * 2)
+            field_descs.append(FieldDescEntry(field_name, field_type,
+                field_size))
+        self.set_record_struct(field_descs)
+        # Pick out the field description indexes we will need
+        success = True
+        try:
+            fi_title = self.find_field(u'Title')
+            fi_author = self.find_field(u'Author')
+            fi_copy = self.find_field(u'Copyright')
+            fi_admin = self.find_field(u'Administrator')
+            fi_words = self.find_field(u'Words')
+            fi_ccli = self.find_field(u'Song Number')
+        except IndexError:
+            # This is the wrong table
+            success = False
+        # Loop through each block of the file
+        cur_block = first_block
+        while cur_block != 0 and success:
+            db_file.seek(header_size + ((cur_block - 1) * 1024 * block_size))
+            cur_block, rec_count = struct.unpack('<h2xh', db_file.read(6))
+            rec_count = (rec_count + record_size) / record_size
+            # Loop through each record within the current block
+            for i in range(rec_count):
+                if self.stop_import_flag:
+                    success = False
+                    break
+                raw_record = db_file.read(record_size)
+                self.fields = self.record_struct.unpack(raw_record)
+                self.set_defaults()
+                self.title = self.get_field(fi_title)
+                self.import_wizard.incrementProgressBar(
+                    u'Importing "%s"...' % self.title, 0)
+                self.copyright = self.get_field(fi_copy) + \
+                    u', Administered by ' + self.get_field(fi_admin)
+                self.ccli_number = self.get_field(fi_ccli)
+                # Format the lyrics
+                if self.stop_import_flag:
+                    success = False
+                    break
+                words = self.get_field(fi_words)
+                words = strip_rtf(words)
+                for verse in words.split(u'\n\n'):
+                    self.add_verse(verse.strip(), u'V')
+                # Split up the authors
+                authors = self.get_field(fi_author)
+                author_list = authors.split(u'/')
+                if len(author_list) < 2:
+                    author_list = authors.split(u',')
+                for author_name in author_list:
+                    self.add_author(author_name.strip())
+                if self.stop_import_flag:
+                    success = False
+                    break
+                self.finish()
+            if not self.stop_import_flag:
+                self.import_wizard.incrementProgressBar(u'')
+        db_file.close()
+        self.memo_file.close()
+        return success
+
+    def find_field(self, field_name):
+        return [i for i,x in enumerate(self.field_descs) \
+            if x.name == field_name][0]
+
+    def set_record_struct(self, field_descs):
+        # Begin with empty field struct list
+        fsl = ['>']
+        for field_desc in field_descs:
+            if field_desc.type == 1:
+                # string
+                fsl.append('%ds' % field_desc.size)
+            elif field_desc.type == 3:
+                # 16-bit int
+                fsl.append('H')
+            elif field_desc.type == 4:
+                # 32-bit int
+                fsl.append('I')
+            elif field_desc.type == 9:
+                # Logical
+                fsl.append('B')
+            elif field_desc.type == 0x0c:
+                # Memo
+                fsl.append('%ds' % field_desc.size)
+            elif field_desc.type == 0x0d:
+                # Blob
+                fsl.append('%ds' % field_desc.size)
+            elif field_desc.type == 0x15:
+                # Timestamp
+                fsl.append('Q')
+            else:
+                fsl.append('%ds' % field_desc.size)
+        self.record_struct = struct.Struct(''.join(fsl))
+        self.field_descs = field_descs
+
+    def get_field(self, field_desc_index):
+        field = self.fields[field_desc_index]
+        field_desc = self.field_descs[field_desc_index]
+        # Check for 'blank' entries
+        if isinstance(field, str):
+            if len(field.rstrip('\0')) == 0:
+                return u''
+        elif field == 0:
+            return 0
+        # Format the field depending on the field type
+        if field_desc.type == 1:
+            # string
+            return field.rstrip('\0').decode(u'windows-1252')
+        elif field_desc.type == 3:
+            # 16-bit int
+            return field ^ 0x8000
+        elif field_desc.type == 4:
+            # 32-bit int
+            return field ^ 0x80000000
+        elif field_desc.type == 9:
+            # Logical
+            return (field ^ 0x80 == 1)
+        elif field_desc.type == 0x0c or field_desc.type == 0x0d:
+            # Memo or Blob
+            sub_block, block_start, blob_size = \
+                struct.unpack_from('<bhxi', field, len(field)-10)
+            self.memo_file.seek(block_start * 256)
+            memo_block_type, = struct.unpack('b', self.memo_file.read(1))
+            if memo_block_type == 2:
+                self.memo_file.seek(8, os.SEEK_CUR)
+            elif memo_block_type == 3:
+                if sub_block < 0 or sub_block > 63:
+                    return u'';
+                self.memo_file.seek(11 + (5 * sub_block), os.SEEK_CUR)
+                sub_block_start, = struct.unpack('B', self.memo_file.read(1))
+                self.memo_file.seek((block_start * 256) +
+                    (sub_block_start * 16))
+            else:
+                return u'';
+            return self.memo_file.read(blob_size)
+        else:
+            return 0

=== modified file 'openlp/plugins/songs/lib/importer.py'
--- openlp/plugins/songs/lib/importer.py	2010-09-14 20:33:33 +0000
+++ openlp/plugins/songs/lib/importer.py	2010-09-20 21:11:03 +0000
@@ -28,6 +28,7 @@
 from olpimport import OpenLPSongImport
 from wowimport import WowImport
 from cclifileimport import CCLIFileImport
+from ewimport import EasyWorshipSongImport
 # Imports that might fail
 try:
     from olp1import import OpenLP1SongImport
@@ -61,7 +62,8 @@
     CCLI = 5
     SongsOfFellowship = 6
     Generic = 7
-    CSV = 8
+    #CSV = 8
+    EasyWorship = 8
 
     @staticmethod
     def get_class(format):
@@ -85,6 +87,8 @@
             return OooImport
         elif format == SongFormat.CCLI:
             return CCLIFileImport
+        elif format == SongFormat.EasyWorship:
+            return EasyWorshipSongImport
 #        else:
         return None
 
@@ -101,7 +105,8 @@
             SongFormat.WordsOfWorship,
             SongFormat.CCLI,
             SongFormat.SongsOfFellowship,
-            SongFormat.Generic
+            SongFormat.Generic,
+            SongFormat.EasyWorship
         ]
 
     @staticmethod


Follow ups