Merge lp:~phill-ridout/openlp/fixes-I into lp:openlp

Proposed by Phill on 2019-03-17
Status: Merged
Merged at revision: 2850
Proposed branch: lp:~phill-ridout/openlp/fixes-I
Merge into: lp:openlp
Diff against target: 1333 lines (+187/-208)
47 files modified
openlp/core/api/deploy.py (+2/-2)
openlp/core/app.py (+8/-4)
openlp/core/common/__init__.py (+2/-2)
openlp/core/common/path.py (+1/-1)
openlp/core/common/settings.py (+1/-1)
openlp/core/display/html/display.html (+1/-1)
openlp/core/display/html/display.js (+1/-1)
openlp/core/lib/__init__.py (+6/-8)
openlp/core/lib/db.py (+15/-11)
openlp/core/lib/mediamanageritem.py (+6/-6)
openlp/core/lib/serviceitem.py (+3/-3)
openlp/core/ui/advancedtab.py (+1/-1)
openlp/core/ui/exceptiondialog.py (+3/-3)
openlp/core/ui/exceptionform.py (+14/-7)
openlp/core/ui/generaltab.py (+0/-1)
openlp/core/ui/mainwindow.py (+1/-1)
openlp/core/ui/servicemanager.py (+13/-12)
openlp/core/ui/thememanager.py (+10/-10)
openlp/core/version.py (+1/-1)
openlp/core/widgets/edits.py (+1/-1)
openlp/core/widgets/wizard.py (+1/-41)
openlp/plugins/bibles/forms/bibleimportform.py (+2/-2)
openlp/plugins/bibles/lib/bibleimport.py (+2/-2)
openlp/plugins/bibles/lib/db.py (+6/-6)
openlp/plugins/bibles/lib/importers/wordproject.py (+1/-1)
openlp/plugins/bibles/lib/manager.py (+1/-1)
openlp/plugins/bibles/lib/mediaitem.py (+3/-3)
openlp/plugins/custom/lib/mediaitem.py (+3/-6)
openlp/plugins/images/lib/mediaitem.py (+3/-3)
openlp/plugins/media/lib/mediaitem.py (+7/-7)
openlp/plugins/presentations/lib/mediaitem.py (+3/-3)
openlp/plugins/presentations/lib/messagelistener.py (+2/-8)
openlp/plugins/songs/forms/songimportform.py (+7/-2)
openlp/plugins/songs/lib/importers/cclifile.py (+4/-3)
openlp/plugins/songs/lib/mediaitem.py (+2/-4)
openlp/plugins/songusage/forms/songusagedetailform.py (+2/-2)
run_openlp.py (+8/-2)
tests/functional/openlp_core/api/test_deploy.py (+2/-2)
tests/functional/openlp_core/common/test_init.py (+14/-8)
tests/functional/openlp_core/common/test_path.py (+3/-5)
tests/functional/openlp_core/lib/test_serviceitem.py (+4/-4)
tests/functional/openlp_core/ui/test_thememanager.py (+3/-3)
tests/functional/openlp_plugins/bibles/test_manager.py (+3/-2)
tests/interfaces/openlp_plugins/songs/forms/test_songmaintenanceform.py (+2/-2)
tests/openlp_core/common/test_network_interfaces.py (+1/-1)
tests/openlp_core/projectors/test_projector_pjlink_commands_01.py (+6/-6)
tests/openlp_core/projectors/test_projector_sourceform.py (+2/-2)
To merge this branch: bzr merge lp:~phill-ridout/openlp/fixes-I
Reviewer Review Type Date Requested Status
Tim Bentley 2019-03-17 Pending
Review via email: mp+364652@code.launchpad.net

This proposal supersedes a proposal from 2019-03-17.

Commit message

Fixes a few bugs, and some path lib refactors

To post a comment you must log in.
Raoul Snyman (raoul-snyman) wrote : Posted in a previous version of this proposal

Linux tests passed!

Raoul Snyman (raoul-snyman) wrote : Posted in a previous version of this proposal

Linting failed, please see https://ci.openlp.io/job/MP-03-Linting/55/ for more details

Raoul Snyman (raoul-snyman) wrote : Posted in a previous version of this proposal

Linux tests passed!

Raoul Snyman (raoul-snyman) wrote : Posted in a previous version of this proposal

Linting failed, please see https://ci.openlp.io/job/MP-03-Linting/56/ for more details

Tim Bentley (trb143) wrote : Posted in a previous version of this proposal

Looks ok.

Please do not try and change vlcplayer as you will be on a mission of despair!

review: Approve
Raoul Snyman (raoul-snyman) wrote :

Linux tests passed!

Raoul Snyman (raoul-snyman) wrote :

Linting passed!

Raoul Snyman (raoul-snyman) wrote :

macOS tests failed, please see https://ci.openlp.io/job/MP-04-macOS-Tests/48/ for more details

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'openlp/core/api/deploy.py'
2--- openlp/core/api/deploy.py 2019-02-14 15:09:09 +0000
3+++ openlp/core/api/deploy.py 2019-03-17 21:06:27 +0000
4@@ -39,8 +39,8 @@
5 :return: None
6 """
7 zip_path = app_root_path / zip_name
8- web_zip = ZipFile(str(zip_path))
9- web_zip.extractall(str(app_root_path))
10+ web_zip = ZipFile(zip_path)
11+ web_zip.extractall(app_root_path)
12
13
14 def download_sha256():
15
16=== modified file 'openlp/core/app.py'
17--- openlp/core/app.py 2019-02-14 15:09:09 +0000
18+++ openlp/core/app.py 2019-03-17 21:06:27 +0000
19@@ -317,8 +317,7 @@
20 """
21 create_paths(log_path, do_not_log=True)
22 file_path = log_path / 'openlp.log'
23- # TODO: FileHandler accepts a Path object in Py3.6
24- logfile = logging.FileHandler(str(file_path), 'w', encoding='UTF-8')
25+ logfile = logging.FileHandler(file_path, 'w', encoding='UTF-8')
26 logfile.setFormatter(logging.Formatter('%(asctime)s %(threadName)s %(name)-55s %(levelname)-8s %(message)s'))
27 log.addHandler(logfile)
28 if log.isEnabledFor(logging.DEBUG):
29@@ -364,7 +363,7 @@
30 portable_settings_path = data_path / 'OpenLP.ini'
31 # Make this our settings file
32 log.info('INI file: {name}'.format(name=portable_settings_path))
33- Settings.set_filename(str(portable_settings_path))
34+ Settings.set_filename(portable_settings_path)
35 portable_settings = Settings()
36 # Set our data path
37 log.info('Data path: {name}'.format(name=data_path))
38@@ -405,7 +404,12 @@
39 None, translate('OpenLP', 'Settings Upgrade'),
40 translate('OpenLP', 'Your settings are about to be upgraded. A backup will be created at '
41 '{back_up_path}').format(back_up_path=back_up_path))
42- settings.export(back_up_path)
43+ try:
44+ settings.export(back_up_path)
45+ except OSError:
46+ QtWidgets.QMessageBox.warning(
47+ None, translate('OpenLP', 'Settings Upgrade'),
48+ translate('OpenLP', 'Settings back up failed.\n\nContinuining to upgrade.'))
49 settings.upgrade_settings()
50 # First time checks in settings
51 if not Settings().value('core/has run wizard'):
52
53=== modified file 'openlp/core/common/__init__.py'
54--- openlp/core/common/__init__.py 2019-02-14 15:09:09 +0000
55+++ openlp/core/common/__init__.py 2019-03-17 21:06:27 +0000
56@@ -474,10 +474,10 @@
57 if not chunk:
58 break
59 detector.feed(chunk)
60- detector.close()
61- return detector.result
62 except OSError:
63 log.exception('Error detecting file encoding')
64+ finally:
65+ return detector.close()
66
67
68 def normalize_str(irregular_string):
69
70=== modified file 'openlp/core/common/path.py'
71--- openlp/core/common/path.py 2019-02-14 15:09:09 +0000
72+++ openlp/core/common/path.py 2019-03-17 21:06:27 +0000
73@@ -78,7 +78,7 @@
74 :param onerror: Handler function to handle any errors
75 :rtype: None
76 """
77- shutil.rmtree(str(self), ignore_errors, onerror)
78+ shutil.rmtree(self, ignore_errors, onerror)
79
80
81 def replace_params(args, kwargs, params):
82
83=== modified file 'openlp/core/common/settings.py'
84--- openlp/core/common/settings.py 2019-02-14 15:09:09 +0000
85+++ openlp/core/common/settings.py 2019-03-17 21:06:27 +0000
86@@ -540,7 +540,7 @@
87 old_values = [self._convert_value(old_value, default_value)
88 for old_value, default_value in zip(old_values, default_values)]
89 # Iterate over our rules and check what the old_value should be "converted" to.
90- new_value = None
91+ new_value = old_values[0]
92 for new_rule, old_rule in rules:
93 # If the value matches with the condition (rule), then use the provided value. This is used to
94 # convert values. E. g. an old value 1 results in True, and 0 in False.
95
96=== modified file 'openlp/core/display/html/display.html'
97--- openlp/core/display/html/display.html 2018-10-12 19:51:51 +0000
98+++ openlp/core/display/html/display.html 2019-03-17 21:06:27 +0000
99@@ -6,7 +6,7 @@
100 <style type="text/css">
101 body {
102 background: transparent !important;
103- color: #fff !important;
104+ color: rgb(255, 255, 255) !important;
105 }
106 sup {
107 vertical-align: super !important;
108
109=== modified file 'openlp/core/display/html/display.js'
110--- openlp/core/display/html/display.js 2019-02-05 21:26:30 +0000
111+++ openlp/core/display/html/display.js 2019-03-17 21:06:27 +0000
112@@ -315,7 +315,7 @@
113 section.setAttribute("style", "height: 100%; width: 100%; position: relative;");
114 var img = document.createElement('img');
115 img.src = image;
116- img.setAttribute("style", "position: absolute; top: 0; bottom: 0; left: 0; right: 0; margin: auto;");
117+ img.setAttribute("style", "position: absolute; top: 0; bottom: 0; left: 0; right: 0; margin: auto; max-height: 100%; max-width: 100%");
118 section.appendChild(img);
119 slidesDiv.appendChild(section);
120 Display._slides['0'] = 0;
121
122=== modified file 'openlp/core/lib/__init__.py'
123--- openlp/core/lib/__init__.py 2019-02-14 15:09:09 +0000
124+++ openlp/core/lib/__init__.py 2019-03-17 21:06:27 +0000
125@@ -242,16 +242,16 @@
126 """
127 Resize an image to fit on the current screen for the web and returns it as a byte stream.
128
129- :param image: The image to converted.
130+ :param image: The image to be converted.
131 :param base_64: If True returns the image as Base64 bytes, otherwise the image is returned as a byte array.
132 To preserve original intention, this defaults to True
133 """
134 log.debug('image_to_byte - start')
135 byte_array = QtCore.QByteArray()
136 # use buffer to store pixmap into byteArray
137- buffie = QtCore.QBuffer(byte_array)
138- buffie.open(QtCore.QIODevice.WriteOnly)
139- image.save(buffie, "PNG")
140+ buffer = QtCore.QBuffer(byte_array)
141+ buffer.open(QtCore.QIODevice.WriteOnly)
142+ image.save(buffer, "PNG")
143 log.debug('image_to_byte - end')
144 if not base_64:
145 return byte_array
146@@ -263,15 +263,13 @@
147 """
148 Create a thumbnail from the given image path and depending on ``return_icon`` it returns an icon from this thumb.
149
150- :param image_path: The image file to create the icon from.
151- :param thumb_path: The filename to save the thumbnail to.
152+ :param openlp.core.common.path.Path image_path: The image file to create the icon from.
153+ :param openlp.core.common.path.Path thumb_path: The filename to save the thumbnail to.
154 :param return_icon: States if an icon should be build and returned from the thumb. Defaults to ``True``.
155 :param size: Allows to state a own size (QtCore.QSize) to use. Defaults to ``None``, which means that a default
156 height of 88 is used.
157 :return: The final icon.
158 """
159- # TODO: To path object
160- thumb_path = Path(thumb_path)
161 reader = QtGui.QImageReader(str(image_path))
162 if size is None:
163 # No size given; use default height of 88
164
165=== modified file 'openlp/core/lib/db.py'
166--- openlp/core/lib/db.py 2019-02-14 15:09:09 +0000
167+++ openlp/core/lib/db.py 2019-03-17 21:06:27 +0000
168@@ -132,8 +132,9 @@
169 Create a path to a database from the plugin name and database name
170
171 :param plugin_name: Name of plugin
172- :param db_file_name: File name of database
173- :return: The path to the database as type str
174+ :param openlp.core.common.path.Path | str | None db_file_name: File name of database
175+ :return: The path to the database
176+ :rtype: str
177 """
178 if db_file_name is None:
179 return 'sqlite:///{path}/{plugin}.sqlite'.format(path=AppLocation.get_section_data_path(plugin_name),
180@@ -144,15 +145,15 @@
181 return 'sqlite:///{path}/{name}'.format(path=AppLocation.get_section_data_path(plugin_name), name=db_file_name)
182
183
184-def handle_db_error(plugin_name, db_file_name):
185+def handle_db_error(plugin_name, db_file_path):
186 """
187 Log and report to the user that a database cannot be loaded
188
189 :param plugin_name: Name of plugin
190- :param db_file_name: File name of database
191+ :param openlp.core.common.path.Path db_file_path: File name of database
192 :return: None
193 """
194- db_path = get_db_path(plugin_name, db_file_name)
195+ db_path = get_db_path(plugin_name, db_file_path)
196 log.exception('Error loading database: {db}'.format(db=db_path))
197 critical_error_message_box(translate('OpenLP.Manager', 'Database Error'),
198 translate('OpenLP.Manager',
199@@ -161,10 +162,13 @@
200
201 def init_url(plugin_name, db_file_name=None):
202 """
203- Return the database URL.
204+ Construct the connection string for a database.
205
206 :param plugin_name: The name of the plugin for the database creation.
207- :param db_file_name: The database file name. Defaults to None resulting in the plugin_name being used.
208+ :param openlp.core.common.path.Path | str | None db_file_name: The database file name. Defaults to None resulting
209+ in the plugin_name being used.
210+ :return: The database URL
211+ :rtype: str
212 """
213 settings = Settings()
214 settings.beginGroup(plugin_name)
215@@ -345,7 +349,7 @@
216 Runs the initialisation process that includes creating the connection to the database and the tables if they do
217 not exist.
218
219- :param plugin_name: The name to setup paths and settings section names
220+ :param plugin_name: The name to setup paths and settings section names
221 :param init_schema: The init_schema function for this database
222 :param openlp.core.common.path.Path db_file_path: The file name to use for this database. Defaults to None
223 resulting in the plugin_name being used.
224@@ -357,14 +361,14 @@
225 self.db_url = None
226 if db_file_path:
227 log.debug('Manager: Creating new DB url')
228- self.db_url = init_url(plugin_name, str(db_file_path))
229+ self.db_url = init_url(plugin_name, str(db_file_path)) # TOdO :PATHLIB
230 else:
231 self.db_url = init_url(plugin_name)
232 if upgrade_mod:
233 try:
234 db_ver, up_ver = upgrade_db(self.db_url, upgrade_mod)
235 except (SQLAlchemyError, DBAPIError):
236- handle_db_error(plugin_name, str(db_file_path))
237+ handle_db_error(plugin_name, db_file_path)
238 return
239 if db_ver > up_ver:
240 critical_error_message_box(
241@@ -379,7 +383,7 @@
242 try:
243 self.session = init_schema(self.db_url)
244 except (SQLAlchemyError, DBAPIError):
245- handle_db_error(plugin_name, str(db_file_path))
246+ handle_db_error(plugin_name, db_file_path)
247 else:
248 self.session = session
249
250
251=== modified file 'openlp/core/lib/mediamanageritem.py'
252--- openlp/core/lib/mediamanageritem.py 2019-02-14 15:09:09 +0000
253+++ openlp/core/lib/mediamanageritem.py 2019-03-17 21:06:27 +0000
254@@ -454,15 +454,16 @@
255 """
256 pass
257
258- def generate_slide_data(self, service_item, item=None, xml_version=False, remote=False,
259- context=ServiceItemContext.Live):
260+ def generate_slide_data(self, service_item, *, item=None, remote=False, context=ServiceItemContext.Live,
261+ file_path=None):
262 """
263 Generate the slide data. Needs to be implemented by the plugin.
264+
265 :param service_item: The service Item to be processed
266 :param item: The database item to be used to build the service item
267- :param xml_version:
268 :param remote: Was this remote triggered (False)
269 :param context: The service context
270+ :param openlp.core.common.path.Path file_path:
271 """
272 raise NotImplementedError('MediaManagerItem.generate_slide_data needs to be defined by the plugin')
273
274@@ -624,17 +625,16 @@
275 'You must select a {title} '
276 'service item.').format(title=self.title))
277
278- def build_service_item(self, item=None, xml_version=False, remote=False, context=ServiceItemContext.Live):
279+ def build_service_item(self, item=None, remote=False, context=ServiceItemContext.Live):
280 """
281 Common method for generating a service item
282 :param item: Service Item to be built.
283- :param xml_version: version of XML (False)
284 :param remote: Remote triggered (False)
285 :param context: The context on which this is called
286 """
287 service_item = ServiceItem(self.plugin)
288 service_item.add_icon()
289- if self.generate_slide_data(service_item, item, xml_version, remote, context):
290+ if self.generate_slide_data(service_item, item=item, remote=remote, context=context):
291 return service_item
292 else:
293 return None
294
295=== modified file 'openlp/core/lib/serviceitem.py'
296--- openlp/core/lib/serviceitem.py 2019-02-14 15:09:09 +0000
297+++ openlp/core/lib/serviceitem.py 2019-03-17 21:06:27 +0000
298@@ -263,7 +263,7 @@
299 file_location = os.path.join(path, file_name)
300 file_location_hash = md5_hash(file_location.encode('utf-8'))
301 image = os.path.join(str(AppLocation.get_section_data_path(self.name)), 'thumbnails',
302- file_location_hash, ntpath.basename(image))
303+ file_location_hash, ntpath.basename(image)) # TODO: Pathlib
304 self.slides.append({'title': file_name, 'image': image, 'path': path, 'display_title': display_title,
305 'notes': notes, 'thumbnail': image})
306 # if self.is_capable(ItemCapabilities.HasThumbnails):
307@@ -361,7 +361,7 @@
308 if isinstance(file_path, str):
309 # Handle service files prior to OpenLP 3.0
310 # Windows can handle both forward and backward slashes, so we use ntpath to get the basename
311- file_path = Path(path, ntpath.basename(file_path))
312+ file_path = path / ntpath.basename(file_path)
313 self.background_audio.append(file_path)
314 self.theme_overwritten = header.get('theme_overwritten', False)
315 if self.service_item_type == ServiceItemType.Text:
316@@ -374,7 +374,7 @@
317 if path:
318 self.has_original_files = False
319 for text_image in service_item['serviceitem']['data']:
320- file_path = os.path.join(path, text_image)
321+ file_path = path / text_image
322 self.add_from_image(file_path, text_image, background)
323 else:
324 for text_image in service_item['serviceitem']['data']:
325
326=== modified file 'openlp/core/ui/advancedtab.py'
327--- openlp/core/ui/advancedtab.py 2019-03-09 03:53:20 +0000
328+++ openlp/core/ui/advancedtab.py 2019-03-17 21:06:27 +0000
329@@ -478,7 +478,7 @@
330 minute=self.service_name_time.time().minute()
331 )
332 try:
333- service_name_example = format_time(str(self.service_name_edit.text()), local_time)
334+ service_name_example = format_time(self.service_name_edit.text(), local_time)
335 except ValueError:
336 preset_is_valid = False
337 service_name_example = translate('OpenLP.AdvancedTab', 'Syntax error.')
338
339=== modified file 'openlp/core/ui/exceptiondialog.py'
340--- openlp/core/ui/exceptiondialog.py 2019-02-14 15:09:09 +0000
341+++ openlp/core/ui/exceptiondialog.py 2019-03-17 21:06:27 +0000
342@@ -77,11 +77,11 @@
343 self.save_report_button = create_button(exception_dialog, 'save_report_button',
344 icon=UiIcons().save,
345 click=self.on_save_report_button_clicked)
346- self.attach_tile_button = create_button(exception_dialog, 'attach_tile_button',
347+ self.attach_file_button = create_button(exception_dialog, 'attach_file_button',
348 icon=UiIcons().open,
349 click=self.on_attach_file_button_clicked)
350 self.button_box = create_button_box(exception_dialog, 'button_box', ['close'],
351- [self.send_report_button, self.save_report_button, self.attach_tile_button])
352+ [self.send_report_button, self.save_report_button, self.attach_file_button])
353 self.exception_layout.addWidget(self.button_box)
354
355 self.retranslate_ui(exception_dialog)
356@@ -112,4 +112,4 @@
357 ).format(first_part=exception_part1))
358 self.send_report_button.setText(translate('OpenLP.ExceptionDialog', 'Send E-Mail'))
359 self.save_report_button.setText(translate('OpenLP.ExceptionDialog', 'Save to File'))
360- self.attach_tile_button.setText(translate('OpenLP.ExceptionDialog', 'Attach File'))
361+ self.attach_file_button.setText(translate('OpenLP.ExceptionDialog', 'Attach File'))
362
363=== modified file 'openlp/core/ui/exceptionform.py'
364--- openlp/core/ui/exceptionform.py 2019-02-14 15:09:09 +0000
365+++ openlp/core/ui/exceptionform.py 2019-03-17 21:06:27 +0000
366@@ -95,12 +95,14 @@
367 """
368 Saving exception log and system information to a file.
369 """
370- file_path, filter_used = FileDialog.getSaveFileName(
371- self,
372- translate('OpenLP.ExceptionForm', 'Save Crash Report'),
373- Settings().value(self.settings_section + '/last directory'),
374- translate('OpenLP.ExceptionForm', 'Text files (*.txt *.log *.text)'))
375- if file_path:
376+ while True:
377+ file_path, filter_used = FileDialog.getSaveFileName(
378+ self,
379+ translate('OpenLP.ExceptionForm', 'Save Crash Report'),
380+ Settings().value(self.settings_section + '/last directory'),
381+ translate('OpenLP.ExceptionForm', 'Text files (*.txt *.log *.text)'))
382+ if file_path is None:
383+ break
384 Settings().setValue(self.settings_section + '/last directory', file_path.parent)
385 opts = self._create_report()
386 report_text = self.report_text.format(version=opts['version'], description=opts['description'],
387@@ -108,8 +110,13 @@
388 try:
389 with file_path.open('w') as report_file:
390 report_file.write(report_text)
391- except OSError:
392+ break
393+ except OSError as e:
394 log.exception('Failed to write crash report')
395+ QtWidgets.QMessageBox.warning(
396+ self, translate('OpenLP.ExceptionDialog', 'Failed to Save Report'),
397+ translate('OpenLP.ExceptionDialog', 'The following error occured when saving the report.\n\n'
398+ '{exception}').format(file_name=file_path, exception=e))
399
400 def on_send_report_button_clicked(self):
401 """
402
403=== modified file 'openlp/core/ui/generaltab.py'
404--- openlp/core/ui/generaltab.py 2019-02-14 15:09:09 +0000
405+++ openlp/core/ui/generaltab.py 2019-03-17 21:06:27 +0000
406@@ -47,7 +47,6 @@
407 """
408 Initialise the general settings tab
409 """
410- self.logo_file = ':/graphics/openlp-splash-screen.png'
411 self.logo_background_color = '#ffffff'
412 self.screens = ScreenList()
413 self.icon_path = ':/icon/openlp-logo.svg'
414
415=== modified file 'openlp/core/ui/mainwindow.py'
416--- openlp/core/ui/mainwindow.py 2019-02-14 15:09:09 +0000
417+++ openlp/core/ui/mainwindow.py 2019-03-17 21:06:27 +0000
418@@ -1334,7 +1334,7 @@
419 self.show_status_message(
420 translate('OpenLP.MainWindow', 'Copying OpenLP data to new data directory location - {path} '
421 '- Please wait for copy to finish').format(path=self.new_data_path))
422- dir_util.copy_tree(str(old_data_path), str(self.new_data_path))
423+ dir_util.copy_tree(old_data_path, self.new_data_path)
424 self.log_info('Copy successful')
425 except (OSError, DistutilsFileError) as why:
426 self.application.set_normal_cursor()
427
428=== modified file 'openlp/core/ui/servicemanager.py'
429--- openlp/core/ui/servicemanager.py 2019-02-14 15:09:09 +0000
430+++ openlp/core/ui/servicemanager.py 2019-03-17 21:06:27 +0000
431@@ -25,7 +25,6 @@
432 import html
433 import json
434 import os
435-import shutil
436 import zipfile
437 from contextlib import suppress
438 from datetime import datetime, timedelta
439@@ -234,7 +233,7 @@
440 self.service_manager_list.itemExpanded.connect(self.expanded)
441 # Last little bits of setting up
442 self.service_theme = Settings().value(self.main_window.service_manager_settings_section + '/service theme')
443- self.service_path = str(AppLocation.get_section_data_path('servicemanager'))
444+ self.service_path = AppLocation.get_section_data_path('servicemanager')
445 # build the drag and drop context menu
446 self.dnd_menu = QtWidgets.QMenu()
447 self.new_action = self.dnd_menu.addAction(translate('OpenLP.ServiceManager', '&Add New Item'))
448@@ -590,11 +589,11 @@
449 self.main_window.increment_progress_bar(service_content_size)
450 # Finally add all the listed media files.
451 for write_path in write_list:
452- zip_file.write(str(write_path), str(write_path))
453+ zip_file.write(write_path, write_path)
454 self.main_window.increment_progress_bar(write_path.stat().st_size)
455 with suppress(FileNotFoundError):
456 file_path.unlink()
457- os.link(temp_file.name, str(file_path))
458+ os.link(temp_file.name, file_path)
459 Settings().setValue(self.main_window.service_manager_settings_section + '/last directory', file_path.parent)
460 except (PermissionError, OSError) as error:
461 self.log_exception('Failed to save service to disk: {name}'.format(name=file_path))
462@@ -679,7 +678,7 @@
463 service_data = None
464 self.application.set_busy_cursor()
465 try:
466- with zipfile.ZipFile(str(file_path)) as zip_file:
467+ with zipfile.ZipFile(file_path) as zip_file:
468 compressed_size = 0
469 for zip_info in zip_file.infolist():
470 compressed_size += zip_info.compress_size
471@@ -692,7 +691,7 @@
472 service_data = json_file.read()
473 else:
474 zip_info.filename = os.path.basename(zip_info.filename)
475- zip_file.extract(zip_info, str(self.service_path))
476+ zip_file.extract(zip_info, self.service_path)
477 self.main_window.increment_progress_bar(zip_info.compress_size)
478 if service_data:
479 items = json.loads(service_data, cls=OpenLPJsonDecoder)
480@@ -705,11 +704,13 @@
481 else:
482 raise ValidationError(msg='No service data found')
483 except (NameError, OSError, ValidationError, zipfile.BadZipFile):
484+ self.application.set_normal_cursor()
485 self.log_exception('Problem loading service file {name}'.format(name=file_path))
486 critical_error_message_box(
487 message=translate('OpenLP.ServiceManager',
488- 'The service file {file_path} could not be loaded because it is either corrupt, or '
489- 'not a valid OpenLP 2 or OpenLP 3 service file.'.format(file_path=file_path)))
490+ 'The service file {file_path} could not be loaded because it is either corrupt, '
491+ 'inaccessible, or not a valid OpenLP 2 or OpenLP 3 service file.'
492+ ).format(file_path=file_path))
493 self.main_window.finished_progress_bar()
494 self.application.set_normal_cursor()
495 self.repaint_service_list(-1, -1)
496@@ -1237,11 +1238,11 @@
497 """
498 Empties the service_path of temporary files on system exit.
499 """
500- for file_name in os.listdir(self.service_path):
501- file_path = Path(self.service_path, file_name)
502+ for file_path in self.service_path.iterdir():
503 delete_file(file_path)
504- if os.path.exists(os.path.join(self.service_path, 'audio')):
505- shutil.rmtree(os.path.join(self.service_path, 'audio'), True)
506+ audio_path = self.service_path / 'audio'
507+ if audio_path.exists():
508+ audio_path.rmtree(True)
509
510 def on_theme_combo_box_selected(self, current_index):
511 """
512
513=== modified file 'openlp/core/ui/thememanager.py'
514--- openlp/core/ui/thememanager.py 2019-02-14 15:09:09 +0000
515+++ openlp/core/ui/thememanager.py 2019-03-17 21:06:27 +0000
516@@ -150,7 +150,7 @@
517 self.global_theme = Settings().value(self.settings_section + '/global theme')
518 self.build_theme_path()
519 self.load_first_time_themes()
520- self.upgrade_themes()
521+ self.upgrade_themes() # TODO: Can be removed when upgrade path from OpenLP 2.4 no longer needed
522
523 def bootstrap_post_set_up(self):
524 """
525@@ -422,10 +422,10 @@
526 :rtype: bool
527 """
528 try:
529- with zipfile.ZipFile(str(theme_path), 'w') as theme_zip:
530+ with zipfile.ZipFile(theme_path, 'w') as theme_zip:
531 source_path = self.theme_path / theme_name
532 for file_path in source_path.iterdir():
533- theme_zip.write(str(file_path), os.path.join(theme_name, file_path.name))
534+ theme_zip.write(file_path, Path(theme_name, file_path.name))
535 return True
536 except OSError as ose:
537 self.log_exception('Export Theme Failed')
538@@ -567,10 +567,10 @@
539 json_theme = False
540 theme_name = ""
541 try:
542- with zipfile.ZipFile(str(file_path)) as theme_zip:
543+ with zipfile.ZipFile(file_path) as theme_zip:
544 json_file = [name for name in theme_zip.namelist() if os.path.splitext(name)[1].lower() == '.json']
545 if len(json_file) != 1:
546- # TODO: remove XML handling after the 2.6 release.
547+ # TODO: remove XML handling after once the upgrade path from 2.4 is no longer required
548 xml_file = [name for name in theme_zip.namelist() if os.path.splitext(name)[1].lower() == '.xml']
549 if len(xml_file) != 1:
550 self.log_error('Theme contains "{val:d}" theme files'.format(val=len(xml_file)))
551@@ -607,12 +607,12 @@
552 else:
553 with full_name.open('wb') as out_file:
554 out_file.write(theme_zip.read(zipped_file))
555- except (OSError, zipfile.BadZipFile):
556+ except (OSError, ValidationError, zipfile.BadZipFile):
557 self.log_exception('Importing theme from zip failed {name}'.format(name=file_path))
558- raise ValidationError
559- except ValidationError:
560- critical_error_message_box(translate('OpenLP.ThemeManager', 'Validation Error'),
561- translate('OpenLP.ThemeManager', 'File is not a valid theme.'))
562+ critical_error_message_box(
563+ translate('OpenLP.ThemeManager', 'Import Error'),
564+ translate('OpenLP.ThemeManager', 'There was a problem imoorting {file_name}.\n\nIt is corrupt,'
565+ 'inaccessible or not a valid theme.').format(file_name=file_path))
566 finally:
567 if not abort_import:
568 # As all files are closed, we can create the Theme.
569
570=== modified file 'openlp/core/version.py'
571--- openlp/core/version.py 2019-02-14 15:09:09 +0000
572+++ openlp/core/version.py 2019-03-17 21:06:27 +0000
573@@ -200,7 +200,7 @@
574 """
575 library_versions = OrderedDict([(library, _get_lib_version(*args)) for library, args in LIBRARIES.items()])
576 # Just update the dict for display purposes, changing the None to a '-'
577- for library, version in library_versions:
578+ for library, version in library_versions.items():
579 if version is None:
580 library_versions[library] = '-'
581 return library_versions
582
583=== modified file 'openlp/core/widgets/edits.py'
584--- openlp/core/widgets/edits.py 2019-02-14 15:09:09 +0000
585+++ openlp/core/widgets/edits.py 2019-03-17 21:06:27 +0000
586@@ -352,7 +352,7 @@
587 :rtype: None
588 """
589 if self._path != path:
590- self._path = path
591+ self.path = path
592 self.pathChanged.emit(path)
593
594
595
596=== modified file 'openlp/core/widgets/wizard.py'
597--- openlp/core/widgets/wizard.py 2019-02-14 15:09:09 +0000
598+++ openlp/core/widgets/wizard.py 2019-03-17 21:06:27 +0000
599@@ -27,13 +27,11 @@
600 from PyQt5 import QtCore, QtGui, QtWidgets
601
602 from openlp.core.common import is_macosx
603-from openlp.core.common.i18n import UiStrings, translate
604+from openlp.core.common.i18n import translate
605 from openlp.core.common.mixins import RegistryProperties
606 from openlp.core.common.registry import Registry
607-from openlp.core.common.settings import Settings
608 from openlp.core.lib.ui import add_welcome_page
609 from openlp.core.ui.icons import UiIcons
610-from openlp.core.widgets.dialogs import FileDialog
611
612
613 log = logging.getLogger(__name__)
614@@ -280,41 +278,3 @@
615 self.finish_button.setVisible(True)
616 self.cancel_button.setVisible(False)
617 self.application.process_events()
618-
619- def get_file_name(self, title, editbox, setting_name, filters=''):
620- """
621- Opens a FileDialog and saves the filename to the given editbox.
622-
623- :param str title: The title of the dialog.
624- :param QtWidgets.QLineEdit editbox: An QLineEdit.
625- :param str setting_name: The place where to save the last opened directory.
626- :param str filters: The file extension filters. It should contain the file description
627- as well as the file extension. For example::
628-
629- 'OpenLP 2 Databases (*.sqlite)'
630- :rtype: None
631- """
632- if filters:
633- filters += ';;'
634- filters += '%s (*)' % UiStrings().AllFiles
635- file_path, filter_used = FileDialog.getOpenFileName(
636- self, title, Settings().value(self.plugin.settings_section + '/' + setting_name), filters)
637- if file_path:
638- editbox.setText(str(file_path))
639- Settings().setValue(self.plugin.settings_section + '/' + setting_name, file_path.parent)
640-
641- def get_folder(self, title, editbox, setting_name):
642- """
643- Opens a FileDialog and saves the selected folder to the given editbox.
644-
645- :param str title: The title of the dialog.
646- :param QtWidgets.QLineEdit editbox: An QLineEditbox.
647- :param str setting_name: The place where to save the last opened directory.
648- :rtype: None
649- """
650- folder_path = FileDialog.getExistingDirectory(
651- self, title, Settings().value(self.plugin.settings_section + '/' + setting_name),
652- FileDialog.ShowDirsOnly)
653- if folder_path:
654- editbox.setText(str(folder_path))
655- Settings().setValue(self.plugin.settings_section + '/' + setting_name, folder_path)
656
657=== modified file 'openlp/plugins/bibles/forms/bibleimportform.py'
658--- openlp/plugins/bibles/forms/bibleimportform.py 2019-02-14 15:09:09 +0000
659+++ openlp/plugins/bibles/forms/bibleimportform.py 2019-03-17 21:06:27 +0000
660@@ -463,14 +463,14 @@
661 UiStrings().NFSs, translate('BiblesPlugin.ImportWizardForm',
662 'You need to specify a file with books of the Bible to use in the '
663 'import.'))
664- self.csv_books_edit.setFocus()
665+ self.csv_books_path_edit.setFocus()
666 return False
667 elif not self.field('csv_versefile'):
668 critical_error_message_box(
669 UiStrings().NFSs,
670 translate('BiblesPlugin.ImportWizardForm', 'You need to specify a file of Bible verses to '
671 'import.'))
672- self.csv_verses_edit.setFocus()
673+ self.csv_verses_pathedit.setFocus()
674 return False
675 elif self.field('source_format') == BibleFormat.OpenSong:
676 if not self.field('opensong_file'):
677
678=== modified file 'openlp/plugins/bibles/lib/bibleimport.py'
679--- openlp/plugins/bibles/lib/bibleimport.py 2019-02-14 15:09:09 +0000
680+++ openlp/plugins/bibles/lib/bibleimport.py 2019-03-17 21:06:27 +0000
681@@ -48,9 +48,9 @@
682 """
683 Check if the supplied file is compressed
684
685- :param file_path: A path to the file to check
686+ :param openlp.core.common.path.Path file_path: A path to the file to check
687 """
688- if is_zipfile(str(file_path)):
689+ if is_zipfile(file_path):
690 critical_error_message_box(
691 message=translate('BiblesPlugin.BibleImport',
692 'The file "{file}" you supplied is compressed. You must decompress it before import.'
693
694=== modified file 'openlp/plugins/bibles/lib/db.py'
695--- openlp/plugins/bibles/lib/db.py 2019-02-14 15:09:09 +0000
696+++ openlp/plugins/bibles/lib/db.py 2019-03-17 21:06:27 +0000
697@@ -158,11 +158,10 @@
698 self.name = kwargs['name']
699 if not isinstance(self.name, str):
700 self.name = str(self.name, 'utf-8')
701- # TODO: To path object
702- file_path = Path(clean_filename(self.name) + '.sqlite')
703+ self.file_path = Path(clean_filename(self.name) + '.sqlite')
704 if 'file' in kwargs:
705- file_path = kwargs['file']
706- Manager.__init__(self, 'bibles', init_schema, file_path, upgrade)
707+ self.file_path = kwargs['file']
708+ Manager.__init__(self, 'bibles', init_schema, self.file_path, upgrade)
709 if self.session and 'file' in kwargs:
710 self.get_name()
711 self._is_web_bible = None
712@@ -750,7 +749,7 @@
713 ]
714
715
716-class AlternativeBookNamesDB(QtCore.QObject, Manager):
717+class AlternativeBookNamesDB(Manager):
718 """
719 This class represents a database-bound alternative book names system.
720 """
721@@ -765,8 +764,9 @@
722 """
723 if AlternativeBookNamesDB.cursor is None:
724 file_path = AppLocation.get_directory(AppLocation.DataDir) / 'bibles' / 'alternative_book_names.sqlite'
725+ exists = file_path.exists()
726 AlternativeBookNamesDB.conn = sqlite3.connect(str(file_path))
727- if not file_path.exists():
728+ if not exists:
729 # create new DB, create table alternative_book_names
730 AlternativeBookNamesDB.conn.execute(
731 'CREATE TABLE alternative_book_names(id INTEGER NOT NULL, '
732
733=== modified file 'openlp/plugins/bibles/lib/importers/wordproject.py'
734--- openlp/plugins/bibles/lib/importers/wordproject.py 2019-02-14 15:09:09 +0000
735+++ openlp/plugins/bibles/lib/importers/wordproject.py 2019-03-17 21:06:27 +0000
736@@ -51,7 +51,7 @@
737 Unzip the file to a temporary directory
738 """
739 self.tmp = TemporaryDirectory()
740- with ZipFile(str(self.file_path)) as zip_file:
741+ with ZipFile(self.file_path) as zip_file:
742 zip_file.extractall(self.tmp.name)
743 self.base_path = Path(self.tmp.name, self.file_path.stem)
744
745
746=== modified file 'openlp/plugins/bibles/lib/manager.py'
747--- openlp/plugins/bibles/lib/manager.py 2019-02-14 15:09:09 +0000
748+++ openlp/plugins/bibles/lib/manager.py 2019-03-17 21:06:27 +0000
749@@ -187,7 +187,7 @@
750 bible = self.db_cache[name]
751 bible.session.close_all()
752 bible.session = None
753- return delete_file(Path(bible.path, bible.file))
754+ return delete_file(bible.path, bible.file_path)
755
756 def get_bibles(self):
757 """
758
759=== modified file 'openlp/plugins/bibles/lib/mediaitem.py'
760--- openlp/plugins/bibles/lib/mediaitem.py 2019-02-14 15:09:09 +0000
761+++ openlp/plugins/bibles/lib/mediaitem.py 2019-03-17 21:06:27 +0000
762@@ -911,16 +911,16 @@
763 list_widget_items.append(bible_verse)
764 return list_widget_items
765
766- def generate_slide_data(self, service_item, item=None, xml_version=False, remote=False,
767- context=ServiceItemContext.Service):
768+ def generate_slide_data(self, service_item, *, item=None, remote=False, context=ServiceItemContext.Service,
769+ **kwargs):
770 """
771 Generate the slide data. Needs to be implemented by the plugin.
772
773 :param service_item: The service item to be built on
774 :param item: The Song item to be used
775- :param xml_version: The xml version (not used)
776 :param remote: Triggered from remote
777 :param context: Why is it being generated
778+ :param kwargs: Consume other unused args specified by the base implementation, but not use by this one.
779 """
780 log.debug('generating slide data')
781 if item:
782
783=== modified file 'openlp/plugins/custom/lib/mediaitem.py'
784--- openlp/plugins/custom/lib/mediaitem.py 2019-02-14 15:09:09 +0000
785+++ openlp/plugins/custom/lib/mediaitem.py 2019-03-17 21:06:27 +0000
786@@ -28,7 +28,7 @@
787 from openlp.core.common.i18n import UiStrings, translate
788 from openlp.core.common.registry import Registry
789 from openlp.core.common.settings import Settings
790-from openlp.core.lib import ServiceItemContext, check_item_selected
791+from openlp.core.lib import check_item_selected
792 from openlp.core.lib.mediamanageritem import MediaManagerItem
793 from openlp.core.lib.plugin import PluginStatus
794 from openlp.core.lib.serviceitem import ItemCapabilities
795@@ -219,15 +219,12 @@
796 self.search_text_edit.setFocus()
797 self.search_text_edit.selectAll()
798
799- def generate_slide_data(self, service_item, item=None, xml_version=False,
800- remote=False, context=ServiceItemContext.Service):
801+ def generate_slide_data(self, service_item, *, item=None, **kwargs):
802 """
803 Generate the slide data. Needs to be implemented by the plugin.
804 :param service_item: To be updated
805 :param item: The custom database item to be used
806- :param xml_version: No used
807- :param remote: Is this triggered by the Preview Controller or Service Manager.
808- :param context: Why is this item required to be build (Default Service).
809+ :param kwargs: Consume other unused args specified by the base implementation, but not use by this one.
810 """
811 item_id = self._get_id_of_item_to_generate(item, self.remote_custom)
812 service_item.add_capability(ItemCapabilities.CanEdit)
813
814=== modified file 'openlp/plugins/images/lib/mediaitem.py'
815--- openlp/plugins/images/lib/mediaitem.py 2019-02-14 15:09:09 +0000
816+++ openlp/plugins/images/lib/mediaitem.py 2019-03-17 21:06:27 +0000
817@@ -542,16 +542,16 @@
818 image_items.sort(key=lambda item: get_natural_key(item.text(0)))
819 target_group.addChildren(image_items)
820
821- def generate_slide_data(self, service_item, item=None, xml_version=False, remote=False,
822- context=ServiceItemContext.Service):
823+ def generate_slide_data(self, service_item, *, item=None, remote=False, context=ServiceItemContext.Service,
824+ **kwargs):
825 """
826 Generate the slide data. Needs to be implemented by the plugin.
827
828 :param service_item: The service item to be built on
829 :param item: The Song item to be used
830- :param xml_version: The xml version (not used)
831 :param remote: Triggered from remote
832 :param context: Why is it being generated
833+ :param kwargs: Consume other unused args specified by the base implementation, but not use by this one.
834 """
835 background = QtGui.QColor(Settings().value(self.settings_section + '/background color'))
836 if item:
837
838=== modified file 'openlp/plugins/media/lib/mediaitem.py'
839--- openlp/plugins/media/lib/mediaitem.py 2019-02-14 15:09:09 +0000
840+++ openlp/plugins/media/lib/mediaitem.py 2019-03-17 21:06:27 +0000
841@@ -29,7 +29,7 @@
842 from openlp.core.common.applocation import AppLocation
843 from openlp.core.common.i18n import UiStrings, get_natural_key, translate
844 from openlp.core.common.mixins import RegistryProperties
845-from openlp.core.common.path import Path, create_paths, path_to_str
846+from openlp.core.common.path import create_paths, path_to_str
847 from openlp.core.common.registry import Registry
848 from openlp.core.common.settings import Settings
849 from openlp.core.lib import MediaType, ServiceItemContext, check_item_selected
850@@ -166,16 +166,16 @@
851 # self.display_type_combo_box.currentIndexChanged.connect(self.override_player_changed)
852 pass
853
854- def generate_slide_data(self, service_item, item=None, xml_version=False, remote=False,
855- context=ServiceItemContext.Service):
856+ def generate_slide_data(self, service_item, *, item=None, remote=False, context=ServiceItemContext.Service,
857+ **kwargs):
858 """
859 Generate the slide data. Needs to be implemented by the plugin.
860
861 :param service_item: The service item to be built on
862 :param item: The Song item to be used
863- :param xml_version: The xml version (not used)
864 :param remote: Triggered from remote
865 :param context: Why is it being generated
866+ :param kwargs: Consume other unused args specified by the base implementation, but not use by this one.
867 """
868 if item is None:
869 item = self.list_view.currentItem()
870@@ -229,8 +229,8 @@
871 Initialize media item.
872 """
873 self.list_view.clear()
874- self.service_path = str(AppLocation.get_section_data_path(self.settings_section) / 'thumbnails')
875- create_paths(Path(self.service_path))
876+ self.service_path = AppLocation.get_section_data_path(self.settings_section) / 'thumbnails'
877+ create_paths(self.service_path)
878 self.load_list([path_to_str(file) for file in Settings().value(self.settings_section + '/media files')])
879 self.rebuild_players()
880
881@@ -264,7 +264,7 @@
882 :param media: The media
883 :param target_group:
884 """
885- media.sort(key=lambda file_name: get_natural_key(os.path.split(str(file_name))[1]))
886+ media.sort(key=lambda file_path: get_natural_key(file_path.name))
887 for track in media:
888 track_info = QtCore.QFileInfo(track)
889 item_name = None
890
891=== modified file 'openlp/plugins/presentations/lib/mediaitem.py'
892--- openlp/plugins/presentations/lib/mediaitem.py 2019-02-14 15:09:09 +0000
893+++ openlp/plugins/presentations/lib/mediaitem.py 2019-03-17 21:06:27 +0000
894@@ -260,16 +260,16 @@
895 doc.presentation_deleted()
896 doc.close_presentation()
897
898- def generate_slide_data(self, service_item, item=None, xml_version=False, remote=False,
899- context=ServiceItemContext.Service, file_path=None):
900+ def generate_slide_data(self, service_item, *, item=None, remote=False, context=ServiceItemContext.Service,
901+ file_path=None, **kwargs):
902 """
903 Generate the slide data. Needs to be implemented by the plugin.
904
905 :param service_item: The service item to be built on
906 :param item: The Song item to be used
907- :param xml_version: The xml version (not used)
908 :param remote: Triggered from remote
909 :param context: Why is it being generated
910+ :param kwargs: Consume other unused args specified by the base implementation, but not use by this one.
911 """
912 if item:
913 items = [item]
914
915=== modified file 'openlp/plugins/presentations/lib/messagelistener.py'
916--- openlp/plugins/presentations/lib/messagelistener.py 2019-02-14 15:09:09 +0000
917+++ openlp/plugins/presentations/lib/messagelistener.py 2019-03-17 21:06:27 +0000
918@@ -337,14 +337,8 @@
919 # Create a copy of the original item, and then clear the original item so it can be filled with images
920 item_cpy = copy.copy(item)
921 item.__init__(None)
922- if is_live:
923- # TODO: To Path object
924- self.media_item.generate_slide_data(item, item_cpy, False, False, ServiceItemContext.Live,
925- str(file_path))
926- else:
927- # TODO: To Path object
928- self.media_item.generate_slide_data(item, item_cpy, False, False, ServiceItemContext.Preview,
929- str(file_path))
930+ context = ServiceItemContext.Live if is_live else ServiceItemContext.Preview
931+ self.media_item.generate_slide_data(item, item=item_cpy, context=context, file_path=file_path)
932 # Some of the original serviceitem attributes is needed in the new serviceitem
933 item.footer = item_cpy.footer
934 item.from_service = item_cpy.from_service
935
936=== modified file 'openlp/plugins/songs/forms/songimportform.py'
937--- openlp/plugins/songs/forms/songimportform.py 2019-02-14 15:09:09 +0000
938+++ openlp/plugins/songs/forms/songimportform.py 2019-03-17 21:06:27 +0000
939@@ -329,8 +329,13 @@
940 importer = self.plugin.import_songs(
941 source_format,
942 file_paths=self.get_list_of_paths(self.format_widgets[source_format]['file_list_widget']))
943- importer.do_import()
944- self.progress_label.setText(WizardStrings.FinishedImport)
945+ try:
946+ importer.do_import()
947+ self.progress_label.setText(WizardStrings.FinishedImport)
948+ except OSError as e:
949+ log.exception('Importing songs failed')
950+ self.progress_label.setText(translate('SongsPlugin.ImportWizardForm',
951+ 'Your Song import failed. {error}').format(error=e))
952
953 def on_error_copy_to_button_clicked(self):
954 """
955
956=== modified file 'openlp/plugins/songs/lib/importers/cclifile.py'
957--- openlp/plugins/songs/lib/importers/cclifile.py 2019-02-14 15:09:09 +0000
958+++ openlp/plugins/songs/lib/importers/cclifile.py 2019-03-17 21:06:27 +0000
959@@ -67,7 +67,7 @@
960 details = {'confidence': 1, 'encoding': 'utf-8'}
961 except UnicodeDecodeError:
962 details = chardet.detect(detect_content)
963- in_file = codecs.open(str(file_path), 'r', details['encoding'])
964+ in_file = codecs.open(file_path, 'r', details['encoding'])
965 if not in_file.read(1) == '\ufeff':
966 # not UTF or no BOM was found
967 in_file.seek(0)
968@@ -251,10 +251,10 @@
969 line_number = 0
970 check_first_verse_line = False
971 verse_text = ''
972+ verse_type = VerseType.tags[VerseType.Verse]
973 song_author = ''
974 verse_start = False
975 for line in text_list:
976- verse_type = 'v'
977 clean_line = line.strip()
978 if not clean_line:
979 if line_number == 0:
980@@ -263,6 +263,7 @@
981 if verse_text:
982 self.add_verse(verse_text, verse_type)
983 verse_text = ''
984+ verse_type = VerseType.tags[VerseType.Verse]
985 verse_start = False
986 else:
987 # line_number=0, song title
988@@ -279,7 +280,7 @@
989 elif not verse_start:
990 # We have the verse descriptor
991 verse_desc_parts = clean_line.split(' ')
992- if len(verse_desc_parts) == 2:
993+ if len(verse_desc_parts):
994 if verse_desc_parts[0].startswith('Ver'):
995 verse_type = VerseType.tags[VerseType.Verse]
996 elif verse_desc_parts[0].startswith('Ch'):
997
998=== modified file 'openlp/plugins/songs/lib/mediaitem.py'
999--- openlp/plugins/songs/lib/mediaitem.py 2019-02-14 15:09:09 +0000
1000+++ openlp/plugins/songs/lib/mediaitem.py 2019-03-17 21:06:27 +0000
1001@@ -557,16 +557,14 @@
1002 self.plugin.manager.save_object(new_song)
1003 self.on_song_list_load()
1004
1005- def generate_slide_data(self, service_item, item=None, xml_version=False, remote=False,
1006- context=ServiceItemContext.Service):
1007+ def generate_slide_data(self, service_item, *, item=None, context=ServiceItemContext.Service, **kwargs):
1008 """
1009 Generate the slide data. Needs to be implemented by the plugin.
1010
1011 :param service_item: The service item to be built on
1012 :param item: The Song item to be used
1013- :param xml_version: The xml version (not used)
1014- :param remote: Triggered from remote
1015 :param context: Why is it being generated
1016+ :param kwargs: Consume other unused args specified by the base implementation, but not use by this one.
1017 """
1018 log.debug('generate_slide_data: {service}, {item}, {remote}'.format(service=service_item, item=item,
1019 remote=self.remote_song))
1020
1021=== modified file 'openlp/plugins/songusage/forms/songusagedetailform.py'
1022--- openlp/plugins/songusage/forms/songusagedetailform.py 2019-02-14 15:09:09 +0000
1023+++ openlp/plugins/songusage/forms/songusagedetailform.py 2019-03-17 21:06:27 +0000
1024@@ -85,7 +85,7 @@
1025 self.main_window.error_message(
1026 translate('SongUsagePlugin.SongUsageDetailForm', 'Output Path Not Selected'),
1027 translate('SongUsagePlugin.SongUsageDetailForm', 'You have not set a valid output location for your'
1028- ' song usage report. \nPlease select an existing path on your computer.')
1029+ ' song usage report.\nPlease select an existing path on your computer.')
1030 )
1031 return
1032 create_paths(path)
1033@@ -112,7 +112,7 @@
1034 self.main_window.information_message(
1035 translate('SongUsagePlugin.SongUsageDetailForm', 'Report Creation'),
1036 translate('SongUsagePlugin.SongUsageDetailForm',
1037- 'Report \n{name} \nhas been successfully created. ').format(name=report_file_name)
1038+ 'Report\n{name}\nhas been successfully created.').format(name=report_file_name)
1039 )
1040 except OSError as ose:
1041 log.exception('Failed to write out song usage records')
1042
1043=== modified file 'run_openlp.py'
1044--- run_openlp.py 2019-02-14 15:09:09 +0000
1045+++ run_openlp.py 2019-03-17 21:06:27 +0000
1046@@ -24,6 +24,7 @@
1047 The entrypoint for OpenLP
1048 """
1049 import faulthandler
1050+import logging
1051 import multiprocessing
1052 import sys
1053
1054@@ -34,14 +35,19 @@
1055 from openlp.core.common.applocation import AppLocation
1056 from openlp.core.common.path import create_paths
1057
1058+log = logging.getLogger(__name__)
1059+
1060
1061 def set_up_fault_handling():
1062 """
1063 Set up the Python fault handler
1064 """
1065 # Create the cache directory if it doesn't exist, and enable the fault handler to log to an error log file
1066- create_paths(AppLocation.get_directory(AppLocation.CacheDir))
1067- faulthandler.enable((AppLocation.get_directory(AppLocation.CacheDir) / 'error.log').open('wb'))
1068+ try:
1069+ create_paths(AppLocation.get_directory(AppLocation.CacheDir))
1070+ faulthandler.enable((AppLocation.get_directory(AppLocation.CacheDir) / 'error.log').open('wb'))
1071+ except OSError:
1072+ log.exception('An exception occurred when enabling the fault handler')
1073
1074
1075 def start():
1076
1077=== modified file 'tests/functional/openlp_core/api/test_deploy.py'
1078--- tests/functional/openlp_core/api/test_deploy.py 2019-02-14 21:19:26 +0000
1079+++ tests/functional/openlp_core/api/test_deploy.py 2019-03-17 21:06:27 +0000
1080@@ -63,8 +63,8 @@
1081 deploy_zipfile(root_path, 'site.zip')
1082
1083 # THEN: the zip file should have been extracted to the right location
1084- MockZipFile.assert_called_once_with(root_path_str + os.sep + 'site.zip')
1085- mocked_zipfile.extractall.assert_called_once_with(root_path_str)
1086+ MockZipFile.assert_called_once_with(Path('/tmp/remotes/site.zip'))
1087+ mocked_zipfile.extractall.assert_called_once_with(Path('/tmp/remotes'))
1088
1089 @patch('openlp.core.api.deploy.Registry')
1090 @patch('openlp.core.api.deploy.get_web_page')
1091
1092=== modified file 'tests/functional/openlp_core/common/test_init.py'
1093--- tests/functional/openlp_core/common/test_init.py 2019-02-14 15:09:09 +0000
1094+++ tests/functional/openlp_core/common/test_init.py 2019-03-17 21:06:27 +0000
1095@@ -309,9 +309,9 @@
1096 """
1097 # GIVEN: A mocked UniversalDetector instance with done attribute set to True after first iteration
1098 with patch('openlp.core.common.UniversalDetector') as mocked_universal_detector, \
1099- patch.object(Path, 'open', return_value=BytesIO(b"data" * 260)) as mocked_open:
1100+ patch.object(Path, 'open', return_value=BytesIO(b'data' * 260)) as mocked_open:
1101 encoding_result = {'encoding': 'UTF-8', 'confidence': 0.99}
1102- mocked_universal_detector_inst = MagicMock(result=encoding_result)
1103+ mocked_universal_detector_inst = MagicMock(**{'close.return_value': encoding_result})
1104 type(mocked_universal_detector_inst).done = PropertyMock(side_effect=[False, True])
1105 mocked_universal_detector.return_value = mocked_universal_detector_inst
1106
1107@@ -320,7 +320,7 @@
1108
1109 # THEN: The feed method of UniversalDetector should only br called once before returning a result
1110 mocked_open.assert_called_once_with('rb')
1111- assert mocked_universal_detector_inst.feed.mock_calls == [call(b"data" * 256)]
1112+ assert mocked_universal_detector_inst.feed.mock_calls == [call(b'data' * 256)]
1113 mocked_universal_detector_inst.close.assert_called_once_with()
1114 assert result == encoding_result
1115
1116@@ -331,10 +331,10 @@
1117 # GIVEN: A mocked UniversalDetector instance which isn't set to done and a mocked open, with 1040 bytes of test
1118 # data (enough to run the iterator twice)
1119 with patch('openlp.core.common.UniversalDetector') as mocked_universal_detector, \
1120- patch.object(Path, 'open', return_value=BytesIO(b"data" * 260)) as mocked_open:
1121+ patch.object(Path, 'open', return_value=BytesIO(b'data' * 260)) as mocked_open:
1122 encoding_result = {'encoding': 'UTF-8', 'confidence': 0.99}
1123 mocked_universal_detector_inst = MagicMock(mock=mocked_universal_detector,
1124- **{'done': False, 'result': encoding_result})
1125+ **{'done': False, 'close.return_value': encoding_result})
1126 mocked_universal_detector.return_value = mocked_universal_detector_inst
1127
1128 # WHEN: Calling get_file_encoding
1129@@ -342,7 +342,7 @@
1130
1131 # THEN: The feed method of UniversalDetector should have been called twice before returning a result
1132 mocked_open.assert_called_once_with('rb')
1133- assert mocked_universal_detector_inst.feed.mock_calls == [call(b"data" * 256), call(b"data" * 4)]
1134+ assert mocked_universal_detector_inst.feed.mock_calls == [call(b'data' * 256), call(b'data' * 4)]
1135 mocked_universal_detector_inst.close.assert_called_once_with()
1136 assert result == encoding_result
1137
1138@@ -352,13 +352,19 @@
1139 """
1140 # GIVEN: A mocked UniversalDetector instance which isn't set to done and a mocked open, with 1040 bytes of test
1141 # data (enough to run the iterator twice)
1142- with patch('openlp.core.common.UniversalDetector'), \
1143+ with patch('openlp.core.common.UniversalDetector') as mocked_universal_detector, \
1144 patch('builtins.open', side_effect=OSError), \
1145 patch('openlp.core.common.log') as mocked_log:
1146+ encoding_result = {'encoding': 'UTF-8', 'confidence': 0.99}
1147+ mocked_universal_detector_inst = MagicMock(mock=mocked_universal_detector,
1148+ **{'done': False, 'close.return_value': encoding_result})
1149+ mocked_universal_detector.return_value = mocked_universal_detector_inst
1150
1151 # WHEN: Calling get_file_encoding
1152 result = get_file_encoding(Path('file name'))
1153
1154 # THEN: log.exception should be called and get_file_encoding should return None
1155 mocked_log.exception.assert_called_once_with('Error detecting file encoding')
1156- assert result is None
1157+ mocked_universal_detector_inst.feed.assert_not_called()
1158+ mocked_universal_detector_inst.close.assert_called_once_with()
1159+ assert result == encoding_result
1160
1161=== modified file 'tests/functional/openlp_core/common/test_path.py'
1162--- tests/functional/openlp_core/common/test_path.py 2019-02-14 21:19:26 +0000
1163+++ tests/functional/openlp_core/common/test_path.py 2019-03-17 21:06:27 +0000
1164@@ -179,9 +179,8 @@
1165 # WHEN: Calling :func:`openlp.core.common.path.rmtree` with the path parameter as Path object type
1166 path.rmtree()
1167
1168- # THEN: :func:`shutil.rmtree` should have been called with the str equivalents of the Path object.
1169- mocked_shutil_rmtree.assert_called_once_with(
1170- os.path.join('test', 'path'), False, None)
1171+ # THEN: :func:`shutil.rmtree` should have been called with the the Path object.
1172+ mocked_shutil_rmtree.assert_called_once_with(Path('test', 'path'), False, None)
1173
1174 def test_rmtree_optional_params(self):
1175 """
1176@@ -198,8 +197,7 @@
1177
1178 # THEN: :func:`shutil.rmtree` should have been called with the optional parameters, with out any of the
1179 # values being modified
1180- mocked_shutil_rmtree.assert_called_once_with(
1181- os.path.join('test', 'path'), True, mocked_on_error)
1182+ mocked_shutil_rmtree.assert_called_once_with(path, True, mocked_on_error)
1183
1184 def test_which_no_command(self):
1185 """
1186
1187=== modified file 'tests/functional/openlp_core/lib/test_serviceitem.py'
1188--- tests/functional/openlp_core/lib/test_serviceitem.py 2019-02-14 15:09:09 +0000
1189+++ tests/functional/openlp_core/lib/test_serviceitem.py 2019-03-17 21:06:27 +0000
1190@@ -141,7 +141,7 @@
1191 """
1192 # GIVEN: A new service item and a mocked add icon function
1193 image_name = 'image_1.jpg'
1194- test_file = os.path.join(str(TEST_PATH), image_name)
1195+ test_file = TEST_PATH / image_name
1196 frame_array = {'path': test_file, 'title': image_name}
1197
1198 service_item = ServiceItem(None)
1199@@ -154,13 +154,13 @@
1200 mocked_get_section_data_path:
1201 mocked_exists.return_value = True
1202 mocked_get_section_data_path.return_value = Path('/path/')
1203- service_item.set_from_service(line, str(TEST_PATH))
1204+ service_item.set_from_service(line, TEST_PATH)
1205
1206 # THEN: We should get back a valid service item
1207 assert service_item.is_valid is True, 'The new service item should be valid'
1208 assert test_file == service_item.get_rendered_frame(0), 'The first frame should match the path to the image'
1209 assert frame_array == service_item.get_frames()[0], 'The return should match frame array1'
1210- assert test_file == str(service_item.get_frame_path(0)), \
1211+ assert test_file == service_item.get_frame_path(0), \
1212 'The frame path should match the full path to the image'
1213 assert image_name == service_item.get_frame_title(0), 'The frame title should match the image name'
1214 assert image_name == service_item.get_display_title(), 'The display title should match the first image name'
1215@@ -328,7 +328,7 @@
1216
1217 # WHEN: We add a custom from a saved service
1218 line = convert_file_service_item(TEST_PATH, 'serviceitem-song-linked-audio.osj')
1219- service_item.set_from_service(line, '/test/')
1220+ service_item.set_from_service(line, Path('/test/'))
1221
1222 # THEN: We should get back a valid service item
1223 assert service_item.is_valid is True, 'The new service item should be valid'
1224
1225=== modified file 'tests/functional/openlp_core/ui/test_thememanager.py'
1226--- tests/functional/openlp_core/ui/test_thememanager.py 2019-02-14 15:09:09 +0000
1227+++ tests/functional/openlp_core/ui/test_thememanager.py 2019-03-17 21:06:27 +0000
1228@@ -66,9 +66,9 @@
1229 theme_manager._export_theme(Path('some', 'path', 'Default.otz'), 'Default')
1230
1231 # THEN: The zipfile should be created at the given path
1232- mocked_zipfile_init.assert_called_with(os.path.join('some', 'path', 'Default.otz'), 'w')
1233- mocked_zipfile_write.assert_called_with(str(RESOURCE_PATH / 'themes' / 'Default' / 'Default.xml'),
1234- os.path.join('Default', 'Default.xml'))
1235+ mocked_zipfile_init.assert_called_with(Path('some', 'path', 'Default.otz'), 'w')
1236+ mocked_zipfile_write.assert_called_with(RESOURCE_PATH / 'themes' / 'Default' / 'Default.xml',
1237+ Path('Default', 'Default.xml'))
1238
1239 def test_initial_theme_manager(self):
1240 """
1241
1242=== modified file 'tests/functional/openlp_plugins/bibles/test_manager.py'
1243--- tests/functional/openlp_plugins/bibles/test_manager.py 2019-02-14 15:09:09 +0000
1244+++ tests/functional/openlp_plugins/bibles/test_manager.py 2019-03-17 21:06:27 +0000
1245@@ -55,7 +55,8 @@
1246 instance = BibleManager(MagicMock())
1247 # We need to keep a reference to the mock for close_all as it gets set to None later on!
1248 mocked_close_all = MagicMock()
1249- mocked_bible = MagicMock(file='KJV.sqlite', path='bibles', **{'session.close_all': mocked_close_all})
1250+ mocked_bible = MagicMock(file_path='KJV.sqlite', path=Path('bibles'),
1251+ **{'session.close_all': mocked_close_all})
1252 instance.db_cache = {'KJV': mocked_bible}
1253
1254 # WHEN: Calling delete_bible with 'KJV'
1255@@ -66,4 +67,4 @@
1256 assert result is True
1257 mocked_close_all.assert_called_once_with()
1258 assert mocked_bible.session is None
1259- mocked_delete_file.assert_called_once_with(Path('bibles', 'KJV.sqlite'))
1260+ mocked_delete_file.assert_called_once_with(Path('bibles'), 'KJV.sqlite')
1261
1262=== modified file 'tests/interfaces/openlp_plugins/songs/forms/test_songmaintenanceform.py'
1263--- tests/interfaces/openlp_plugins/songs/forms/test_songmaintenanceform.py 2019-02-14 15:09:09 +0000
1264+++ tests/interfaces/openlp_plugins/songs/forms/test_songmaintenanceform.py 2019-03-17 21:06:27 +0000
1265@@ -236,8 +236,8 @@
1266 assert MockedQListWidgetItem.call_args_list == expected_widget_item_calls, MockedQListWidgetItem.call_args_list
1267 mocked_author_item1.setData.assert_called_once_with(QtCore.Qt.UserRole, 2)
1268 mocked_author_item2.setData.assert_called_once_with(QtCore.Qt.UserRole, 1)
1269- mocked_authors_list_widget.addItem.call_args_list == [
1270- call(mocked_author_item1), call(mocked_author_item2)]
1271+ mocked_authors_list_widget.addItem.assert_has_calls([
1272+ call(mocked_author_item1), call(mocked_author_item2)])
1273
1274 @patch('openlp.plugins.songs.forms.songmaintenanceform.QtWidgets.QListWidgetItem')
1275 @patch('openlp.plugins.songs.forms.songmaintenanceform.Topic')
1276
1277=== modified file 'tests/openlp_core/common/test_network_interfaces.py'
1278--- tests/openlp_core/common/test_network_interfaces.py 2019-02-14 15:09:09 +0000
1279+++ tests/openlp_core/common/test_network_interfaces.py 2019-03-17 21:06:27 +0000
1280@@ -70,7 +70,7 @@
1281 """
1282 Return a QFlags enum with IsUp and IsRunning
1283 """
1284- return (QNetworkInterface.IsUp | QNetworkInterface.IsRunning)
1285+ return QNetworkInterface.IsUp | QNetworkInterface.IsRunning
1286
1287 def name(self):
1288 return self.my_name
1289
1290=== modified file 'tests/openlp_core/projectors/test_projector_pjlink_commands_01.py'
1291--- tests/openlp_core/projectors/test_projector_pjlink_commands_01.py 2019-03-08 15:19:57 +0000
1292+++ tests/openlp_core/projectors/test_projector_pjlink_commands_01.py 2019-03-17 21:06:27 +0000
1293@@ -605,9 +605,9 @@
1294
1295 # THEN: Power should be set to ON
1296 assert pjlink.power == S_STANDBY, 'Power should not have changed'
1297- assert mock_UpdateIcons.emit.called is False, 'projectorUpdateIcons() should not have been called'
1298- mock_change_status.called is False, 'change_status() should not have been called'
1299- mock_send_command.called is False, 'send_command() should not have been called'
1300+ mock_UpdateIcons.emit.assert_not_called()
1301+ mock_change_status.assert_not_called()
1302+ mock_send_command.assert_not_called()
1303 mock_log.warning.assert_has_calls(log_warn_calls)
1304
1305 def test_projector_process_powr_off(self):
1306@@ -627,9 +627,9 @@
1307
1308 # THEN: Power should be set to ON
1309 assert pjlink.power == S_STANDBY, 'Power should have changed to S_STANDBY'
1310- assert mock_UpdateIcons.emit.called is True, 'projectorUpdateIcons should have been called'
1311- mock_change_status.called is True, 'change_status should have been called'
1312- mock_send_command.called is False, 'send_command should not have been called'
1313+ mock_UpdateIcons.emit.assert_called_with()
1314+ mock_change_status.assert_called_with(313)
1315+ mock_send_command.assert_not_called()
1316
1317 def test_projector_process_rfil_save(self):
1318 """
1319
1320=== modified file 'tests/openlp_core/projectors/test_projector_sourceform.py'
1321--- tests/openlp_core/projectors/test_projector_sourceform.py 2019-02-14 15:09:09 +0000
1322+++ tests/openlp_core/projectors/test_projector_sourceform.py 2019-03-17 21:06:27 +0000
1323@@ -83,8 +83,8 @@
1324 Delete all C++ objects at end so we don't segfault.
1325 """
1326 self.projectordb.session.close()
1327- del(self.projectordb)
1328- del(self.projector)
1329+ del self.projectordb
1330+ del self.projector
1331 retries = 0
1332 while retries < 5:
1333 try: