Merge lp:~m2j/openlp/bug-687638 into lp:openlp

Proposed by Meinert Jordan
Status: Work in progress
Proposed branch: lp:~m2j/openlp/bug-687638
Merge into: lp:openlp
Diff against target: 368 lines (+49/-26)
12 files modified
openlp/core/ui/exceptionform.py (+7/-1)
openlp/core/ui/thememanager.py (+3/-3)
openlp/core/utils/__init__.py (+19/-1)
openlp/plugins/bibles/forms/bibleimportform.py (+2/-3)
openlp/plugins/bibles/lib/mediaitem.py (+3/-3)
openlp/plugins/custom/lib/mediaitem.py (+2/-2)
openlp/plugins/images/lib/mediaitem.py (+3/-3)
openlp/plugins/media/lib/mediaitem.py (+3/-3)
openlp/plugins/presentations/lib/mediaitem.py (+2/-2)
openlp/plugins/songs/forms/songexportform.py (+2/-2)
openlp/plugins/songs/lib/mediaitem.py (+2/-3)
scripts/check_dependencies.py (+1/-0)
To merge this branch: bzr merge lp:~m2j/openlp/bug-687638
Reviewer Review Type Date Requested Status
Raoul Snyman Needs Information
Andreas Preikschat (community) Needs Information
Tim Bentley Approve
Review via email: mp+89098@code.launchpad.net

Commit message

Bug #687638: Use ICU collator by default to overcome issue in Windows locale.strcoll call.

Description of the change

I've splitted a previous merge request. This one contains only ICU integration:

Bug #687638

locale.strcoll does not work correct on Windows with utf-8 [http://sgehrig.wordpress.com/2008/12/08/update-on-strcoll-utf-8-issue/].
The performance ICU is similar to locale.strcoll on Linux (12% faster for a English word list, 10% slower on a German one)

ICU is said to give better results and makes less cross platform troubles. I've implemented it to be ued if available. Fallback is the POSIX locale.strcoll. It seems to be only necessary on Windows, so I suggest to include it only in Windows builds.

To post a comment you must log in.
Revision history for this message
Meinert Jordan (m2j) wrote :

Some more informations:
ICU binaries for Windows have a size of arround 9MB as they contain a lot of data.
I haven't figured out any other way to collide international strings on Windows with comparable performance.
Some information about colliding and Qt can be found on this blog post:
http://labs.qt.nokia.com/2011/06/14/string-collation-with-locales/

Revision history for this message
Raoul Snyman (raoul-snyman) wrote :

This looks OK to me.

review: Approve
Revision history for this message
Andreas Preikschat (googol-deactivatedaccount) wrote :

Maybe somebody with windows should test this?

Revision history for this message
Meinert Jordan (m2j) wrote :

While looking for other i18n things (and figuring out, that Qt just introduced nice methods like QLocale::createSeparatedList()), I stumbled over the fact, that the performance of Qts implementation of collation might be fine as well. Sorting 1500 strings with Python takes about 20ms so that increasing the time by a factor of 5 could be still acceptable.

I will try to find some time to benchmark the Qt algorithm on Windows to see, if this is a alternative to adding so much data to the Windows package.

Revision history for this message
Tim Bentley (trb143) :
review: Approve
Revision history for this message
Andreas Preikschat (googol-deactivatedaccount) wrote :

A question, did you compare the performance? I am not sure, but isn't the function you specify called dozens times?

review: Needs Information
Revision history for this message
Meinert Jordan (m2j) wrote :

I haven't checked it yet, as I have no Windows machine with Python arround ATM.
The only position where we are sorting big lists is in the songs media manager (after editing/importing/deleting a song). Therefore we should have some realtime performance for up to 2000 strings.

Revision history for this message
Andreas Preikschat (googol-deactivatedaccount) wrote :

> The only position where we are sorting big lists is in the songs media manager
> (after editing/importing/deleting a song).

Do not forget the "Search as you type" feature!

Revision history for this message
Raoul Snyman (raoul-snyman) wrote :

Meinert, I'd just like us to do a little more playing around with this before we merge it. In particular I'm thinking of testing out the "Search as you type" speed, and documenting on the wiki how to include ICU in the Windows build.

review: Needs Information
Revision history for this message
Jonathan Corwin (j-corwin) wrote :

Where was this left out of interest? Who is waiting on who?

Revision history for this message
matysek (mzibricky) wrote :

I think this merge can be closed since there is a new pending request to fix related bug.

Revision history for this message
Meinert Jordan (m2j) wrote :

I suggested the use of Qt comparision as well, but as it was reported to have a poor performance, I delayed this until I had time to test the performance under Windows.

For long time I did not take the time, to setup Windows and make a test. So please test the performance on Windows for big databases (~2000 entries), before you relay on this solution.

Revision history for this message
Andreas Preikschat (googol-deactivatedaccount) wrote :

Meinert: any change you pick up with this?

Unmerged revisions

1161. By Meinert Jordan

purge scripture reference code from this branch

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'openlp/core/ui/exceptionform.py'
2--- openlp/core/ui/exceptionform.py 2011-12-27 10:33:55 +0000
3+++ openlp/core/ui/exceptionform.py 2012-01-18 18:04:32 +0000
4@@ -81,6 +81,11 @@
5 WEBKIT_VERSION = QtWebKit.qWebKitVersion()
6 except AttributeError:
7 WEBKIT_VERSION = u'-'
8+try:
9+ import icu
10+ ICU_VERSION = u'OK'
11+except ImportError:
12+ ICU_VERSION = u'-'
13
14
15 from openlp.core.lib import translate, SettingsManager
16@@ -125,7 +130,8 @@
17 u'PyEnchant: %s\n' % ENCHANT_VERSION + \
18 u'PySQLite: %s\n' % SQLITE_VERSION + \
19 u'Mako: %s\n' % MAKO_VERSION + \
20- u'pyUNO bridge: %s\n' % UNO_VERSION
21+ u'pyUNO bridge: %s\n' % UNO_VERSION + \
22+ u'PyICU: %s\n' % ICU_VERSION
23 if platform.system() == u'Linux':
24 if os.environ.get(u'KDE_FULL_SESSION') == u'true':
25 system = system + u'Desktop: KDE SC\n'
26
27=== modified file 'openlp/core/ui/thememanager.py'
28--- openlp/core/ui/thememanager.py 2012-01-04 17:19:49 +0000
29+++ openlp/core/ui/thememanager.py 2012-01-18 18:04:32 +0000
30@@ -29,7 +29,6 @@
31 import zipfile
32 import shutil
33 import logging
34-import locale
35 import re
36
37 from xml.etree.ElementTree import ElementTree, XML
38@@ -44,7 +43,8 @@
39 context_menu_action, context_menu_separator
40 from openlp.core.theme import Theme
41 from openlp.core.ui import FileRenameForm, ThemeForm
42-from openlp.core.utils import AppLocation, delete_file, get_filesystem_encoding
43+from openlp.core.utils import AppLocation, delete_file, \
44+ get_filesystem_encoding, get_local_collator
45
46 log = logging.getLogger(__name__)
47
48@@ -459,7 +459,7 @@
49 # Sort the themes by its name considering language specific characters.
50 # lower() is needed for windows!
51 files.sort(key=lambda filename: unicode(filename).lower(),
52- cmp=locale.strcoll)
53+ cmp=get_local_collator)
54 # now process the file list of png files
55 for name in files:
56 # check to see file is in theme root directory
57
58=== modified file 'openlp/core/utils/__init__.py'
59--- openlp/core/utils/__init__.py 2011-12-27 10:33:55 +0000
60+++ openlp/core/utils/__init__.py 2012-01-18 18:04:32 +0000
61@@ -33,6 +33,7 @@
62 import sys
63 import time
64 import urllib2
65+import locale
66 from datetime import datetime
67 from subprocess import Popen, PIPE
68
69@@ -45,6 +46,17 @@
70 except ImportError:
71 XDG_BASE_AVAILABLE = False
72
73+LOCALE_COLLATOR = locale.strcoll
74+try:
75+ import icu
76+ try:
77+ icu_locale = icu.Locale(locale.getlocale()[0])
78+ LOCALE_COLLATOR = icu.Collator.createInstance(icu_locale).compare
79+ except icu.InvalidArgsError:
80+ pass
81+except ImportError:
82+ pass
83+
84 import openlp
85 from openlp.core.lib import Receiver, translate, check_directory_exists
86
87@@ -482,10 +494,16 @@
88 return resolver.resolve(u'uno:socket,host=localhost,port=2002;' \
89 + u'urp;StarOffice.ComponentContext')
90
91+def get_local_collator(string1, string2):
92+ """
93+ Returns a collator for locale aware string sorting.
94+ """
95+ return LOCALE_COLLATOR(string1, string2)
96+
97 from languagemanager import LanguageManager
98 from actions import ActionList
99
100 __all__ = [u'AppLocation', u'get_application_version', u'check_latest_version',
101 u'add_actions', u'get_filesystem_encoding', u'LanguageManager',
102 u'ActionList', u'get_web_page', u'get_uno_command', u'get_uno_instance',
103- u'delete_file', u'clean_filename']
104+ u'get_local_collator', u'delete_file', u'clean_filename']
105
106=== modified file 'openlp/plugins/bibles/forms/bibleimportform.py'
107--- openlp/plugins/bibles/forms/bibleimportform.py 2011-12-27 10:33:55 +0000
108+++ openlp/plugins/bibles/forms/bibleimportform.py 2012-01-18 18:04:32 +0000
109@@ -30,7 +30,6 @@
110 import logging
111 import os
112 import os.path
113-import locale
114
115 from PyQt4 import QtCore, QtGui
116
117@@ -38,7 +37,7 @@
118 from openlp.core.lib.db import delete_database
119 from openlp.core.lib.ui import UiStrings, critical_error_message_box
120 from openlp.core.ui.wizard import OpenLPWizard, WizardStrings
121-from openlp.core.utils import AppLocation
122+from openlp.core.utils import AppLocation, get_local_collator
123 from openlp.plugins.bibles.lib.manager import BibleFormat
124 from openlp.plugins.bibles.lib.db import BiblesResourcesDB, clean_filename
125
126@@ -522,7 +521,7 @@
127 """
128 self.webTranslationComboBox.clear()
129 bibles = self.web_bible_list[index].keys()
130- bibles.sort(cmp=locale.strcoll)
131+ bibles.sort(cmp=get_local_collator)
132 self.webTranslationComboBox.addItems(bibles)
133
134 def onOsisBrowseButtonClicked(self):
135
136=== modified file 'openlp/plugins/bibles/lib/mediaitem.py'
137--- openlp/plugins/bibles/lib/mediaitem.py 2011-12-27 10:33:55 +0000
138+++ openlp/plugins/bibles/lib/mediaitem.py 2012-01-18 18:04:32 +0000
139@@ -26,7 +26,6 @@
140 ###############################################################################
141
142 import logging
143-import locale
144
145 from PyQt4 import QtCore, QtGui
146
147@@ -36,6 +35,7 @@
148 from openlp.core.lib.ui import UiStrings, add_widget_completer, \
149 media_item_combo_box, critical_error_message_box, \
150 find_and_set_in_combo_box, build_icon
151+from openlp.core.utils import get_local_collator
152 from openlp.plugins.bibles.forms import BibleImportForm
153 from openlp.plugins.bibles.lib import LayoutStyle, DisplayStyle, \
154 VerseReferenceList, get_reference_match
155@@ -373,7 +373,7 @@
156 self.advancedSecondComboBox.addItem(u'')
157 # Get all bibles and sort the list.
158 bibles = self.plugin.manager.get_bibles().keys()
159- bibles.sort(cmp=locale.strcoll)
160+ bibles.sort(cmp=get_local_collator)
161 # Load the bibles into the combo boxes.
162 for bible in bibles:
163 if bible:
164@@ -481,7 +481,7 @@
165 book_data_temp.append(book)
166 book_data = book_data_temp
167 books = [book.name + u' ' for book in book_data]
168- books.sort(cmp=locale.strcoll)
169+ books.sort(cmp=get_local_collator)
170 add_widget_completer(books, self.quickSearchEdit)
171
172 def onQuickVersionComboBox(self):
173
174=== modified file 'openlp/plugins/custom/lib/mediaitem.py'
175--- openlp/plugins/custom/lib/mediaitem.py 2011-12-30 21:40:13 +0000
176+++ openlp/plugins/custom/lib/mediaitem.py 2012-01-18 18:04:32 +0000
177@@ -26,7 +26,6 @@
178 ###############################################################################
179
180 import logging
181-import locale
182
183 from PyQt4 import QtCore, QtGui
184 from sqlalchemy.sql import or_, func
185@@ -34,6 +33,7 @@
186 from openlp.core.lib import MediaManagerItem, Receiver, ItemCapabilities, \
187 check_item_selected, translate
188 from openlp.core.lib.ui import UiStrings
189+from oprnlp.core.utils import get_local_collator
190 from openlp.plugins.custom.forms import EditCustomForm
191 from openlp.plugins.custom.lib import CustomXMLParser
192 from openlp.plugins.custom.lib.db import CustomSlide
193@@ -109,7 +109,7 @@
194 # Sort the customs by its title considering language specific
195 # characters. lower() is needed for windows!
196 custom_slides.sort(
197- cmp=locale.strcoll, key=lambda custom: custom.title.lower())
198+ cmp=get_local_collator, key=lambda custom: custom.title.lower())
199 for custom_slide in custom_slides:
200 custom_name = QtGui.QListWidgetItem(custom_slide.title)
201 custom_name.setData(
202
203=== modified file 'openlp/plugins/images/lib/mediaitem.py'
204--- openlp/plugins/images/lib/mediaitem.py 2011-12-27 10:33:55 +0000
205+++ openlp/plugins/images/lib/mediaitem.py 2012-01-18 18:04:32 +0000
206@@ -27,7 +27,6 @@
207
208 import logging
209 import os
210-import locale
211
212 from PyQt4 import QtCore, QtGui
213
214@@ -35,7 +34,8 @@
215 SettingsManager, translate, check_item_selected, check_directory_exists, \
216 Receiver, create_thumb, validate_thumb
217 from openlp.core.lib.ui import UiStrings, critical_error_message_box
218-from openlp.core.utils import AppLocation, delete_file, get_images_filter
219+from openlp.core.utils import AppLocation, delete_file, get_images_filter, \
220+ get_local_collator
221
222 log = logging.getLogger(__name__)
223
224@@ -120,7 +120,7 @@
225 self.plugin.formparent.displayProgressBar(len(images))
226 # Sort the themes by its filename considering language specific
227 # characters. lower() is needed for windows!
228- images.sort(cmp=locale.strcoll,
229+ images.sort(cmp=get_local_collator,
230 key=lambda filename: os.path.split(unicode(filename))[1].lower())
231 for imageFile in images:
232 if not initialLoad:
233
234=== modified file 'openlp/plugins/media/lib/mediaitem.py'
235--- openlp/plugins/media/lib/mediaitem.py 2011-12-27 10:33:55 +0000
236+++ openlp/plugins/media/lib/mediaitem.py 2012-01-18 18:04:32 +0000
237@@ -27,7 +27,6 @@
238
239 import logging
240 import os
241-import locale
242
243 from PyQt4 import QtCore, QtGui
244
245@@ -37,6 +36,7 @@
246 from openlp.core.lib.ui import UiStrings, critical_error_message_box, \
247 media_item_combo_box
248 from openlp.core.ui import Controller, Display
249+from openlp.core.utils import get_local_collator
250
251 log = logging.getLogger(__name__)
252
253@@ -278,7 +278,7 @@
254 def loadList(self, media):
255 # Sort the themes by its filename considering language specific
256 # characters. lower() is needed for windows!
257- media.sort(cmp=locale.strcoll,
258+ media.sort(cmp=get_local_collator,
259 key=lambda filename: os.path.split(unicode(filename))[1].lower())
260 for track in media:
261 track_info = QtCore.QFileInfo(track)
262@@ -298,7 +298,7 @@
263
264 def getList(self, type=MediaType.Audio):
265 media = SettingsManager.load_list(self.settingsSection, u'media')
266- media.sort(cmp=locale.strcoll,
267+ media.sort(cmp=get_local_collator,
268 key=lambda filename: os.path.split(unicode(filename))[1].lower())
269 ext = []
270 if type == MediaType.Audio:
271
272=== modified file 'openlp/plugins/presentations/lib/mediaitem.py'
273--- openlp/plugins/presentations/lib/mediaitem.py 2011-12-27 10:33:55 +0000
274+++ openlp/plugins/presentations/lib/mediaitem.py 2012-01-18 18:04:32 +0000
275@@ -27,7 +27,6 @@
276
277 import logging
278 import os
279-import locale
280
281 from PyQt4 import QtCore, QtGui
282
283@@ -36,6 +35,7 @@
284 validate_thumb
285 from openlp.core.lib.ui import UiStrings, critical_error_message_box, \
286 media_item_combo_box
287+from openlp.core.utils import get_local_collator
288 from openlp.plugins.presentations.lib import MessageListener
289
290 log = logging.getLogger(__name__)
291@@ -168,7 +168,7 @@
292 self.plugin.formparent.displayProgressBar(len(files))
293 # Sort the themes by its filename considering language specific
294 # characters. lower() is needed for windows!
295- files.sort(cmp=locale.strcoll,
296+ files.sort(cmp=get_local_collator,
297 key=lambda filename: os.path.split(unicode(filename))[1].lower())
298 for file in files:
299 if not initialLoad:
300
301=== modified file 'openlp/plugins/songs/forms/songexportform.py'
302--- openlp/plugins/songs/forms/songexportform.py 2011-12-27 10:33:55 +0000
303+++ openlp/plugins/songs/forms/songexportform.py 2012-01-18 18:04:32 +0000
304@@ -28,7 +28,6 @@
305 The :mod:`songexportform` module provides the wizard for exporting songs to the
306 OpenLyrics format.
307 """
308-import locale
309 import logging
310
311 from PyQt4 import QtCore, QtGui
312@@ -36,6 +35,7 @@
313 from openlp.core.lib import build_icon, Receiver, SettingsManager, translate
314 from openlp.core.lib.ui import UiStrings, critical_error_message_box
315 from openlp.core.ui.wizard import OpenLPWizard, WizardStrings
316+from openlp.core.utils import get_local_collator
317 from openlp.plugins.songs.lib.db import Song
318 from openlp.plugins.songs.lib.openlyricsexport import OpenLyricsExport
319
320@@ -250,7 +250,7 @@
321 # Load the list of songs.
322 Receiver.send_message(u'cursor_busy')
323 songs = self.plugin.manager.get_all_objects(Song)
324- songs.sort(cmp=locale.strcoll, key=lambda song: song.title.lower())
325+ songs.sort(cmp=get_local_collator, key=lambda song: song.title.lower())
326 for song in songs:
327 # No need to export temporary songs.
328 if song.temporary:
329
330=== modified file 'openlp/plugins/songs/lib/mediaitem.py'
331--- openlp/plugins/songs/lib/mediaitem.py 2011-12-30 21:40:13 +0000
332+++ openlp/plugins/songs/lib/mediaitem.py 2012-01-18 18:04:32 +0000
333@@ -26,7 +26,6 @@
334 ###############################################################################
335
336 import logging
337-import locale
338 import re
339 import os
340 import shutil
341@@ -38,7 +37,7 @@
342 translate, check_item_selected, PluginStatus
343 from openlp.core.lib.ui import UiStrings, context_menu_action, \
344 context_menu_separator
345-from openlp.core.utils import AppLocation
346+from openlp.core.utils import AppLocation, get_local_collator
347 from openlp.plugins.songs.forms import EditSongForm, SongMaintenanceForm, \
348 SongImportForm, SongExportForm
349 from openlp.plugins.songs.lib import OpenLyrics, SongXML, VerseType, \
350@@ -240,7 +239,7 @@
351 # Sort the songs by its title considering language specific characters.
352 # lower() is needed for windows!
353 searchresults.sort(
354- cmp=locale.strcoll, key=lambda song: song.title.lower())
355+ cmp=get_local_collator, key=lambda song: song.title.lower())
356 for song in searchresults:
357 # Do not display temporary songs
358 if song.temporary:
359
360=== modified file 'scripts/check_dependencies.py'
361--- scripts/check_dependencies.py 2011-10-26 07:40:12 +0000
362+++ scripts/check_dependencies.py 2012-01-18 18:04:32 +0000
363@@ -74,6 +74,7 @@
364 'mako',
365 'migrate',
366 'uno',
367+ 'icu',
368 ]
369
370