Merge lp:~phill-ridout/openlp/pathlib5 into lp:openlp

Proposed by Phill
Status: Superseded
Proposed branch: lp:~phill-ridout/openlp/pathlib5
Merge into: lp:openlp
Diff against target: 2711 lines (+810/-604)
35 files modified
openlp/core/__init__.py (+18/-25)
openlp/core/common/applocation.py (+0/-1)
openlp/core/common/httputils.py (+20/-22)
openlp/core/common/languagemanager.py (+1/-1)
openlp/core/common/path.py (+116/-0)
openlp/core/common/registry.py (+1/-1)
openlp/core/common/uistrings.py (+0/-3)
openlp/core/lib/__init__.py (+14/-38)
openlp/core/lib/mediamanageritem.py (+0/-2)
openlp/core/ui/firsttimeform.py (+3/-3)
openlp/core/ui/lib/filedialog.py (+1/-2)
openlp/core/ui/lib/wizard.py (+1/-1)
openlp/core/ui/mainwindow.py (+10/-10)
openlp/core/ui/servicemanager.py (+20/-20)
openlp/core/ui/thememanager.py (+1/-1)
openlp/plugins/images/lib/mediaitem.py (+1/-1)
openlp/plugins/presentations/lib/impresscontroller.py (+19/-25)
openlp/plugins/presentations/lib/mediaitem.py (+79/-79)
openlp/plugins/presentations/lib/messagelistener.py (+16/-13)
openlp/plugins/presentations/lib/pdfcontroller.py (+54/-51)
openlp/plugins/presentations/lib/powerpointcontroller.py (+7/-7)
openlp/plugins/presentations/lib/pptviewcontroller.py (+18/-17)
openlp/plugins/presentations/lib/presentationcontroller.py (+83/-71)
openlp/plugins/presentations/lib/presentationtab.py (+5/-4)
openlp/plugins/remotes/deploy.py (+1/-1)
openlp/plugins/songs/reporting.py (+41/-47)
tests/functional/openlp_core_common/test_httputils.py (+2/-1)
tests/functional/openlp_core_common/test_path.py (+203/-2)
tests/functional/openlp_core_lib/test_lib.py (+23/-70)
tests/functional/openlp_core_ui/test_exceptionform.py (+1/-1)
tests/functional/openlp_plugins/presentations/test_impresscontroller.py (+3/-4)
tests/functional/openlp_plugins/presentations/test_mediaitem.py (+10/-9)
tests/functional/openlp_plugins/presentations/test_pdfcontroller.py (+8/-7)
tests/functional/openlp_plugins/presentations/test_pptviewcontroller.py (+7/-7)
tests/functional/openlp_plugins/presentations/test_presentationcontroller.py (+23/-57)
To merge this branch: bzr merge lp:~phill-ridout/openlp/pathlib5
Reviewer Review Type Date Requested Status
Raoul Snyman Pending
Tim Bentley Pending
Review via email: mp+331121@code.launchpad.net

This proposal supersedes a proposal from 2017-09-18.

This proposal has been superseded by a proposal from 2017-09-21.

To post a comment you must log in.
Revision history for this message
Tim Bentley (trb143) wrote : Posted in a previous version of this proposal

Sorry but you removed too much code!

review: Needs Fixing
Revision history for this message
Tim Bentley (trb143) : Posted in a previous version of this proposal
review: Approve
Revision history for this message
Raoul Snyman (raoul-snyman) wrote : Posted in a previous version of this proposal

One request: Please don't create a shutil file, just put those methods into your existing path file.

review: Needs Fixing
lp:~phill-ridout/openlp/pathlib5 updated
2777. By Phill

minor fix

Unmerged revisions

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'openlp/core/__init__.py'
2--- openlp/core/__init__.py 2017-09-03 10:18:14 +0000
3+++ openlp/core/__init__.py 2017-09-21 08:29:37 +0000
4@@ -29,8 +29,6 @@
5
6 import argparse
7 import logging
8-import os
9-import shutil
10 import sys
11 import time
12 from datetime import datetime
13@@ -40,7 +38,7 @@
14
15 from openlp.core.common import Registry, OpenLPMixin, AppLocation, LanguageManager, Settings, UiStrings, \
16 check_directory_exists, is_macosx, is_win, translate
17-from openlp.core.common.path import Path
18+from openlp.core.common.path import Path, copytree
19 from openlp.core.common.versionchecker import VersionThread, get_application_version
20 from openlp.core.lib import ScreenList
21 from openlp.core.resources import qInitResources
22@@ -181,25 +179,20 @@
23 """
24 Check if the data folder path exists.
25 """
26- data_folder_path = str(AppLocation.get_data_path())
27- if not os.path.exists(data_folder_path):
28- log.critical('Database was not found in: ' + data_folder_path)
29- status = QtWidgets.QMessageBox.critical(None, translate('OpenLP', 'Data Directory Error'),
30- translate('OpenLP', 'OpenLP data folder was not found in:\n\n{path}'
31- '\n\nThe location of the data folder was '
32- 'previously changed from the OpenLP\'s '
33- 'default location. If the data was stored on '
34- 'removable device, that device needs to be '
35- 'made available.\n\nYou may reset the data '
36- 'location back to the default location, '
37- 'or you can try to make the current location '
38- 'available.\n\nDo you want to reset to the '
39- 'default data location? If not, OpenLP will be '
40- 'closed so you can try to fix the the problem.')
41- .format(path=data_folder_path),
42- QtWidgets.QMessageBox.StandardButtons(QtWidgets.QMessageBox.Yes |
43- QtWidgets.QMessageBox.No),
44- QtWidgets.QMessageBox.No)
45+ data_folder_path = AppLocation.get_data_path()
46+ if not data_folder_path.exists():
47+ log.critical('Database was not found in: %s', data_folder_path)
48+ status = QtWidgets.QMessageBox.critical(
49+ None, translate('OpenLP', 'Data Directory Error'),
50+ translate('OpenLP', 'OpenLP data folder was not found in:\n\n{path}\n\nThe location of the data folder '
51+ 'was previously changed from the OpenLP\'s default location. If the data was '
52+ 'stored on removable device, that device needs to be made available.\n\nYou may '
53+ 'reset the data location back to the default location, or you can try to make the '
54+ 'current location available.\n\nDo you want to reset to the default data location? '
55+ 'If not, OpenLP will be closed so you can try to fix the the problem.')
56+ .format(path=data_folder_path),
57+ QtWidgets.QMessageBox.StandardButtons(QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No),
58+ QtWidgets.QMessageBox.No)
59 if status == QtWidgets.QMessageBox.No:
60 # If answer was "No", return "True", it will shutdown OpenLP in def main
61 log.info('User requested termination')
62@@ -253,11 +246,11 @@
63 'a backup of the old data folder?'),
64 defaultButton=QtWidgets.QMessageBox.Yes) == QtWidgets.QMessageBox.Yes:
65 # Create copy of data folder
66- data_folder_path = str(AppLocation.get_data_path())
67+ data_folder_path = AppLocation.get_data_path()
68 timestamp = time.strftime("%Y%m%d-%H%M%S")
69- data_folder_backup_path = data_folder_path + '-' + timestamp
70+ data_folder_backup_path = data_folder_path.with_name(data_folder_path.name + '-' + timestamp)
71 try:
72- shutil.copytree(data_folder_path, data_folder_backup_path)
73+ copytree(data_folder_path, data_folder_backup_path)
74 except OSError:
75 QtWidgets.QMessageBox.warning(None, translate('OpenLP', 'Backup'),
76 translate('OpenLP', 'Backup of the data folder failed!'))
77
78=== modified file 'openlp/core/common/applocation.py'
79--- openlp/core/common/applocation.py 2017-08-26 15:06:11 +0000
80+++ openlp/core/common/applocation.py 2017-09-21 08:29:37 +0000
81@@ -29,7 +29,6 @@
82 from openlp.core.common import Settings, is_win, is_macosx
83 from openlp.core.common.path import Path
84
85-
86 if not is_win() and not is_macosx():
87 try:
88 from xdg import BaseDirectory
89
90=== modified file 'openlp/core/common/httputils.py'
91--- openlp/core/common/httputils.py 2017-08-13 05:50:44 +0000
92+++ openlp/core/common/httputils.py 2017-09-21 08:29:37 +0000
93@@ -211,7 +211,7 @@
94
95 :param callback: the class which needs to be updated
96 :param url: URL to download
97- :param f_path: Destination file
98+ :param openlp.core.common.path.Path f_path: Destination file
99 :param sha256: The check sum value to be checked against the download value
100 """
101 block_count = 0
102@@ -220,29 +220,23 @@
103 log.debug("url_get_file: " + url)
104 while True:
105 try:
106- filename = open(f_path, "wb")
107- url_file = urllib.request.urlopen(url, timeout=CONNECTION_TIMEOUT)
108 if sha256:
109 hasher = hashlib.sha256()
110- # Download until finished or canceled.
111- while not callback.was_cancelled:
112- data = url_file.read(block_size)
113- if not data:
114- break
115- filename.write(data)
116- if sha256:
117- hasher.update(data)
118- block_count += 1
119- callback._download_progress(block_count, block_size)
120- filename.close()
121- if sha256 and hasher.hexdigest() != sha256:
122- log.error('sha256 sums did not match for file: {file}'.format(file=f_path))
123- os.remove(f_path)
124- return False
125- except (urllib.error.URLError, socket.timeout) as err:
126+ with f_path.open('wb') as file:
127+ url_file = urllib.request.urlopen(url, timeout=CONNECTION_TIMEOUT)
128+ # Download until finished or canceled.
129+ while not callback.was_cancelled:
130+ data = url_file.read(block_size)
131+ if not data:
132+ break
133+ file.write(data)
134+ if sha256:
135+ hasher.update(data)
136+ block_count += 1
137+ callback._download_progress(block_count, block_size)
138+ except (urllib.error.URLError, socket.timeout):
139 trace_error_handler(log)
140- filename.close()
141- os.remove(f_path)
142+ f_path.unlink()
143 if retries > CONNECTION_RETRIES:
144 return False
145 else:
146@@ -251,8 +245,12 @@
147 continue
148 break
149 # Delete file if cancelled, it may be a partial file.
150+ if sha256 and hasher.hexdigest() != sha256:
151+ log.error('sha256 sums did not match for file: {file}'.format(file=f_path))
152+ f_path.unlink()
153+ return False
154 if callback.was_cancelled:
155- os.remove(f_path)
156+ f_path.unlink()
157 return True
158
159
160
161=== modified file 'openlp/core/common/languagemanager.py'
162--- openlp/core/common/languagemanager.py 2017-08-01 20:59:41 +0000
163+++ openlp/core/common/languagemanager.py 2017-09-21 08:29:37 +0000
164@@ -141,7 +141,7 @@
165 if reg_ex.exactMatch(qmf):
166 name = '{regex}'.format(regex=reg_ex.cap(1))
167 LanguageManager.__qm_list__[
168- '{count:>2i} {name}'.format(count=counter + 1, name=LanguageManager.language_name(qmf))] = name
169+ '{count:>2d} {name}'.format(count=counter + 1, name=LanguageManager.language_name(qmf))] = name
170
171 @staticmethod
172 def get_qm_list():
173
174=== modified file 'openlp/core/common/path.py'
175--- openlp/core/common/path.py 2017-08-27 17:37:54 +0000
176+++ openlp/core/common/path.py 2017-09-21 08:29:37 +0000
177@@ -19,6 +19,7 @@
178 # with this program; if not, write to the Free Software Foundation, Inc., 59 #
179 # Temple Place, Suite 330, Boston, MA 02111-1307 USA #
180 ###############################################################################
181+import shutil
182 from contextlib import suppress
183
184 from openlp.core.common import is_win
185@@ -29,6 +30,121 @@
186 from pathlib import PosixPath as PathVariant
187
188
189+def replace_params(args, kwargs, params):
190+ """
191+ Apply a transformation function to the specified args or kwargs
192+
193+ :param tuple args: Positional arguments
194+ :param dict kwargs: Key Word arguments
195+ :param params: A tuple of tuples with the position and the key word to replace.
196+ :return: The modified positional and keyword arguments
197+ :rtype: tuple[tuple, dict]
198+
199+
200+ Usage:
201+ Take a method with the following signature, and assume we which to apply the str function to arg2:
202+ def method(arg1=None, arg2=None, arg3=None)
203+
204+ As arg2 can be specified postitionally as the second argument (1 with a zero index) or as a keyword, the we
205+ would call this function as follows:
206+
207+ replace_params(args, kwargs, ((1, 'arg2', str),))
208+ """
209+ args = list(args)
210+ for position, key_word, transform in params:
211+ if len(args) > position:
212+ args[position] = transform(args[position])
213+ elif key_word in kwargs:
214+ kwargs[key_word] = transform(kwargs[key_word])
215+ return tuple(args), kwargs
216+
217+
218+def copy(*args, **kwargs):
219+ """
220+ Wraps :func:`shutil.copy` so that we can accept Path objects.
221+
222+ :param src openlp.core.common.path.Path: Takes a Path object which is then converted to a str object
223+ :param dst openlp.core.common.path.Path: Takes a Path object which is then converted to a str object
224+ :return: Converts the str object received from :func:`shutil.copy` to a Path or NoneType object
225+ :rtype: openlp.core.common.path.Path | None
226+
227+ See the following link for more information on the other parameters:
228+ https://docs.python.org/3/library/shutil.html#shutil.copy
229+ """
230+
231+ args, kwargs = replace_params(args, kwargs, ((0, 'src', path_to_str), (1, 'dst', path_to_str)))
232+
233+ return str_to_path(shutil.copy(*args, **kwargs))
234+
235+
236+def copyfile(*args, **kwargs):
237+ """
238+ Wraps :func:`shutil.copyfile` so that we can accept Path objects.
239+
240+ :param openlp.core.common.path.Path src: Takes a Path object which is then converted to a str object
241+ :param openlp.core.common.path.Path dst: Takes a Path object which is then converted to a str object
242+ :return: Converts the str object received from :func:`shutil.copyfile` to a Path or NoneType object
243+ :rtype: openlp.core.common.path.Path | None
244+
245+ See the following link for more information on the other parameters:
246+ https://docs.python.org/3/library/shutil.html#shutil.copyfile
247+ """
248+
249+ args, kwargs = replace_params(args, kwargs, ((0, 'src', path_to_str), (1, 'dst', path_to_str)))
250+
251+ return str_to_path(shutil.copyfile(*args, **kwargs))
252+
253+
254+def copytree(*args, **kwargs):
255+ """
256+ Wraps :func:shutil.copytree` so that we can accept Path objects.
257+
258+ :param openlp.core.common.path.Path src : Takes a Path object which is then converted to a str object
259+ :param openlp.core.common.path.Path dst: Takes a Path object which is then converted to a str object
260+ :return: Converts the str object received from :func:`shutil.copytree` to a Path or NoneType object
261+ :rtype: openlp.core.common.path.Path | None
262+
263+ See the following link for more information on the other parameters:
264+ https://docs.python.org/3/library/shutil.html#shutil.copytree
265+ """
266+
267+ args, kwargs = replace_params(args, kwargs, ((0, 'src', path_to_str), (1, 'dst', path_to_str)))
268+
269+ return str_to_path(shutil.copytree(*args, **kwargs))
270+
271+
272+def rmtree(*args, **kwargs):
273+ """
274+ Wraps :func:shutil.rmtree` so that we can accept Path objects.
275+
276+ :param openlp.core.common.path.Path path: Takes a Path object which is then converted to a str object
277+ :return: Passes the return from :func:`shutil.rmtree` back
278+ :rtype: None
279+
280+ See the following link for more information on the other parameters:
281+ https://docs.python.org/3/library/shutil.html#shutil.rmtree
282+ """
283+
284+ args, kwargs = replace_params(args, kwargs, ((0, 'path', path_to_str),))
285+
286+ return shutil.rmtree(*args, **kwargs)
287+
288+
289+def which(*args, **kwargs):
290+ """
291+ Wraps :func:shutil.which` so that it return a Path objects.
292+
293+ :rtype: openlp.core.common.Path
294+
295+ See the following link for more information on the other parameters:
296+ https://docs.python.org/3/library/shutil.html#shutil.which
297+ """
298+ file_name = shutil.which(*args, **kwargs)
299+ if file_name:
300+ return str_to_path(file_name)
301+ return None
302+
303+
304 def path_to_str(path=None):
305 """
306 A utility function to convert a Path object or NoneType to a string equivalent.
307
308=== modified file 'openlp/core/common/registry.py'
309--- openlp/core/common/registry.py 2017-03-03 19:27:31 +0000
310+++ openlp/core/common/registry.py 2017-09-21 08:29:37 +0000
311@@ -143,7 +143,7 @@
312 log.exception('Exception for function {function}'.format(function=function))
313 else:
314 trace_error_handler(log)
315- log.error("Event {event} called but not registered".format(event=event))
316+ log.exception('Event {event} called but not registered'.format(event=event))
317 return results
318
319 def get_flag(self, key):
320
321=== modified file 'openlp/core/common/uistrings.py'
322--- openlp/core/common/uistrings.py 2017-08-03 17:54:40 +0000
323+++ openlp/core/common/uistrings.py 2017-09-21 08:29:37 +0000
324@@ -88,9 +88,6 @@
325 self.Error = translate('OpenLP.Ui', 'Error')
326 self.Export = translate('OpenLP.Ui', 'Export')
327 self.File = translate('OpenLP.Ui', 'File')
328- self.FileNotFound = translate('OpenLP.Ui', 'File Not Found')
329- self.FileNotFoundMessage = translate('OpenLP.Ui',
330- 'File {name} not found.\nPlease try selecting it individually.')
331 self.FontSizePtUnit = translate('OpenLP.Ui', 'pt', 'Abbreviated font pointsize unit')
332 self.Help = translate('OpenLP.Ui', 'Help')
333 self.Hours = translate('OpenLP.Ui', 'h', 'The abbreviated unit for hours')
334
335=== modified file 'openlp/core/lib/__init__.py'
336--- openlp/core/lib/__init__.py 2017-08-25 20:03:25 +0000
337+++ openlp/core/lib/__init__.py 2017-09-21 08:29:37 +0000
338@@ -32,6 +32,7 @@
339 from PyQt5 import QtCore, QtGui, Qt, QtWidgets
340
341 from openlp.core.common import translate
342+from openlp.core.common.path import Path
343
344 log = logging.getLogger(__name__ + '.__init__')
345
346@@ -125,10 +126,11 @@
347 Build a QIcon instance from an existing QIcon, a resource location, or a physical file location. If the icon is a
348 QIcon instance, that icon is simply returned. If not, it builds a QIcon instance from the resource or file name.
349
350- :param icon:
351- The icon to build. This can be a QIcon, a resource string in the form ``:/resource/file.png``, or a file
352- location like ``/path/to/file.png``. However, the **recommended** way is to specify a resource string.
353+ :param QtGui.QIcon | Path | QtGui.QIcon | str icon:
354+ The icon to build. This can be a QIcon, a resource string in the form ``:/resource/file.png``, or a file path
355+ location like ``Path(/path/to/file.png)``. However, the **recommended** way is to specify a resource string.
356 :return: The build icon.
357+ :rtype: QtGui.QIcon
358 """
359 if isinstance(icon, QtGui.QIcon):
360 return icon
361@@ -136,6 +138,8 @@
362 button_icon = QtGui.QIcon()
363 if isinstance(icon, str):
364 pix_map = QtGui.QPixmap(icon)
365+ elif isinstance(icon, Path):
366+ pix_map = QtGui.QPixmap(str(icon))
367 elif isinstance(icon, QtGui.QImage):
368 pix_map = QtGui.QPixmap.fromImage(icon)
369 if pix_map:
370@@ -217,14 +221,15 @@
371 Validates whether an file's thumb still exists and if is up to date. **Note**, you must **not** call this function,
372 before checking the existence of the file.
373
374- :param file_path: The path to the file. The file **must** exist!
375- :param thumb_path: The path to the thumb.
376- :return: True, False if the image has changed since the thumb was created.
377+ :param openlp.core.common.path.Path file_path: The path to the file. The file **must** exist!
378+ :param openlp.core.common.path.Path thumb_path: The path to the thumb.
379+ :return: Has the image changed since the thumb was created?
380+ :rtype: bool
381 """
382- if not os.path.exists(thumb_path):
383+ if not thumb_path.exists():
384 return False
385- image_date = os.stat(file_path).st_mtime
386- thumb_date = os.stat(thumb_path).st_mtime
387+ image_date = file_path.stat().st_mtime
388+ thumb_date = thumb_path.stat().st_mtime
389 return image_date <= thumb_date
390
391
392@@ -606,35 +611,6 @@
393 return list_to_string
394
395
396-def replace_params(args, kwargs, params):
397- """
398- Apply a transformation function to the specified args or kwargs
399-
400- :param tuple args: Positional arguments
401- :param dict kwargs: Key Word arguments
402- :param params: A tuple of tuples with the position and the key word to replace.
403- :return: The modified positional and keyword arguments
404- :rtype: tuple[tuple, dict]
405-
406-
407- Usage:
408- Take a method with the following signature, and assume we which to apply the str function to arg2:
409- def method(arg1=None, arg2=None, arg3=None)
410-
411- As arg2 can be specified postitionally as the second argument (1 with a zero index) or as a keyword, the we
412- would call this function as follows:
413-
414- replace_params(args, kwargs, ((1, 'arg2', str),))
415- """
416- args = list(args)
417- for position, key_word, transform in params:
418- if len(args) > position:
419- args[position] = transform(args[position])
420- elif key_word in kwargs:
421- kwargs[key_word] = transform(kwargs[key_word])
422- return tuple(args), kwargs
423-
424-
425 from .exceptions import ValidationError
426 from .screen import ScreenList
427 from .formattingtags import FormattingTags
428
429=== modified file 'openlp/core/lib/mediamanageritem.py'
430--- openlp/core/lib/mediamanageritem.py 2017-08-26 15:06:11 +0000
431+++ openlp/core/lib/mediamanageritem.py 2017-09-21 08:29:37 +0000
432@@ -359,10 +359,8 @@
433 :param files: The files to be loaded.
434 :param target_group: The QTreeWidgetItem of the group that will be the parent of the added files
435 """
436- names = []
437 full_list = []
438 for count in range(self.list_view.count()):
439- names.append(self.list_view.item(count).text())
440 full_list.append(self.list_view.item(count).data(QtCore.Qt.UserRole))
441 duplicates_found = False
442 files_added = False
443
444=== modified file 'openlp/core/ui/firsttimeform.py'
445--- openlp/core/ui/firsttimeform.py 2017-08-25 20:03:25 +0000
446+++ openlp/core/ui/firsttimeform.py 2017-09-21 08:29:37 +0000
447@@ -563,7 +563,7 @@
448 filename, sha256 = item.data(QtCore.Qt.UserRole)
449 self._increment_progress_bar(self.downloading.format(name=filename), 0)
450 self.previous_size = 0
451- destination = os.path.join(songs_destination, str(filename))
452+ destination = Path(songs_destination, str(filename))
453 if not url_get_file(self, '{path}{name}'.format(path=self.songs_url, name=filename),
454 destination, sha256):
455 missed_files.append('Song: {name}'.format(name=filename))
456@@ -576,7 +576,7 @@
457 self._increment_progress_bar(self.downloading.format(name=bible), 0)
458 self.previous_size = 0
459 if not url_get_file(self, '{path}{name}'.format(path=self.bibles_url, name=bible),
460- os.path.join(bibles_destination, bible),
461+ Path(bibles_destination, bible),
462 sha256):
463 missed_files.append('Bible: {name}'.format(name=bible))
464 bibles_iterator += 1
465@@ -588,7 +588,7 @@
466 self._increment_progress_bar(self.downloading.format(name=theme), 0)
467 self.previous_size = 0
468 if not url_get_file(self, '{path}{name}'.format(path=self.themes_url, name=theme),
469- os.path.join(themes_destination, theme),
470+ Path(themes_destination, theme),
471 sha256):
472 missed_files.append('Theme: {name}'.format(name=theme))
473 if missed_files:
474
475=== modified file 'openlp/core/ui/lib/filedialog.py'
476--- openlp/core/ui/lib/filedialog.py 2017-08-25 20:03:25 +0000
477+++ openlp/core/ui/lib/filedialog.py 2017-09-21 08:29:37 +0000
478@@ -22,8 +22,7 @@
479 """ Patch the QFileDialog so it accepts and returns Path objects"""
480 from PyQt5 import QtWidgets
481
482-from openlp.core.common.path import Path, path_to_str, str_to_path
483-from openlp.core.lib import replace_params
484+from openlp.core.common.path import Path, path_to_str, replace_params, str_to_path
485
486
487 class FileDialog(QtWidgets.QFileDialog):
488
489=== modified file 'openlp/core/ui/lib/wizard.py'
490--- openlp/core/ui/lib/wizard.py 2017-08-26 15:06:11 +0000
491+++ openlp/core/ui/lib/wizard.py 2017-09-21 08:29:37 +0000
492@@ -310,7 +310,7 @@
493 """
494 folder_path = FileDialog.getExistingDirectory(
495 self, title, Settings().value(self.plugin.settings_section + '/' + setting_name),
496- QtWidgets.QFileDialog.ShowDirsOnly)
497+ FileDialog.ShowDirsOnly)
498 if folder_path:
499 editbox.setText(str(folder_path))
500 Settings().setValue(self.plugin.settings_section + '/' + setting_name, folder_path)
501
502=== modified file 'openlp/core/ui/mainwindow.py'
503--- openlp/core/ui/mainwindow.py 2017-09-03 10:18:14 +0000
504+++ openlp/core/ui/mainwindow.py 2017-09-21 08:29:37 +0000
505@@ -39,7 +39,7 @@
506 from openlp.core.common import Registry, RegistryProperties, AppLocation, LanguageManager, Settings, UiStrings, \
507 check_directory_exists, translate, is_win, is_macosx, add_actions
508 from openlp.core.common.actions import ActionList, CategoryOrder
509-from openlp.core.common.path import Path, path_to_str, str_to_path
510+from openlp.core.common.path import Path, copyfile, path_to_str, str_to_path
511 from openlp.core.common.versionchecker import get_application_version
512 from openlp.core.lib import Renderer, PluginManager, ImageManager, PluginStatus, ScreenList, build_icon
513 from openlp.core.lib.ui import create_action
514@@ -848,12 +848,12 @@
515 QtWidgets.QMessageBox.No)
516 if answer == QtWidgets.QMessageBox.No:
517 return
518- import_file_name, filter_used = QtWidgets.QFileDialog.getOpenFileName(
519+ import_file_path, filter_used = FileDialog.getOpenFileName(
520 self,
521 translate('OpenLP.MainWindow', 'Import settings'),
522- '',
523+ None,
524 translate('OpenLP.MainWindow', 'OpenLP Settings (*.conf)'))
525- if not import_file_name:
526+ if import_file_path is None:
527 return
528 setting_sections = []
529 # Add main sections.
530@@ -871,12 +871,12 @@
531 # Add plugin sections.
532 setting_sections.extend([plugin.name for plugin in self.plugin_manager.plugins])
533 # Copy the settings file to the tmp dir, because we do not want to change the original one.
534- temp_directory = os.path.join(str(gettempdir()), 'openlp')
535- check_directory_exists(Path(temp_directory))
536- temp_config = os.path.join(temp_directory, os.path.basename(import_file_name))
537- shutil.copyfile(import_file_name, temp_config)
538+ temp_dir_path = Path(gettempdir(), 'openlp')
539+ check_directory_exists(temp_dir_path)
540+ temp_config_path = temp_dir_path / import_file_path.name
541+ copyfile(import_file_path, temp_config_path)
542 settings = Settings()
543- import_settings = Settings(temp_config, Settings.IniFormat)
544+ import_settings = Settings(str(temp_config_path), Settings.IniFormat)
545
546 log.info('hook upgrade_plugin_settings')
547 self.plugin_manager.hook_upgrade_plugin_settings(import_settings)
548@@ -920,7 +920,7 @@
549 settings.setValue('{key}'.format(key=section_key), value)
550 now = datetime.now()
551 settings.beginGroup(self.header_section)
552- settings.setValue('file_imported', import_file_name)
553+ settings.setValue('file_imported', import_file_path)
554 settings.setValue('file_date_imported', now.strftime("%Y-%m-%d %H:%M"))
555 settings.endGroup()
556 settings.sync()
557
558=== modified file 'openlp/core/ui/servicemanager.py'
559--- openlp/core/ui/servicemanager.py 2017-08-26 15:06:11 +0000
560+++ openlp/core/ui/servicemanager.py 2017-09-21 08:29:37 +0000
561@@ -366,16 +366,20 @@
562 """
563 return self._modified
564
565- def set_file_name(self, file_name):
566+ def set_file_name(self, file_path):
567 """
568 Setter for service file.
569
570- :param file_name: The service file name
571+ :param openlp.core.common.path.Path file_path: The service file name
572+ :rtype: None
573 """
574- self._file_name = str(file_name)
575+ self._file_name = path_to_str(file_path)
576 self.main_window.set_service_modified(self.is_modified(), self.short_file_name())
577- Settings().setValue('servicemanager/last file', Path(file_name))
578- self._save_lite = self._file_name.endswith('.oszl')
579+ Settings().setValue('servicemanager/last file', file_path)
580+ if file_path and file_path.suffix() == '.oszl':
581+ self._save_lite = True
582+ else:
583+ self._save_lite = False
584
585 def file_name(self):
586 """
587@@ -474,7 +478,7 @@
588 """
589 self.service_manager_list.clear()
590 self.service_items = []
591- self.set_file_name('')
592+ self.set_file_name(None)
593 self.service_id += 1
594 self.set_modified(False)
595 Settings().setValue('servicemanager/last file', None)
596@@ -695,27 +699,23 @@
597 default_file_name = format_time(default_pattern, local_time)
598 else:
599 default_file_name = ''
600- directory = path_to_str(Settings().value(self.main_window.service_manager_settings_section + '/last directory'))
601- path = os.path.join(directory, default_file_name)
602+ directory_path = Settings().value(self.main_window.service_manager_settings_section + '/last directory')
603+ file_path = directory_path / default_file_name
604 # SaveAs from osz to oszl is not valid as the files will be deleted on exit which is not sensible or usable in
605 # the long term.
606 if self._file_name.endswith('oszl') or self.service_has_all_original_files:
607- file_name, filter_used = QtWidgets.QFileDialog.getSaveFileName(
608- self.main_window, UiStrings().SaveService, path,
609+ file_path, filter_used = FileDialog.getSaveFileName(
610+ self.main_window, UiStrings().SaveService, file_path,
611 translate('OpenLP.ServiceManager',
612 'OpenLP Service Files (*.osz);; OpenLP Service Files - lite (*.oszl)'))
613 else:
614- file_name, filter_used = QtWidgets.QFileDialog.getSaveFileName(
615- self.main_window, UiStrings().SaveService, path,
616+ file_path, filter_used = FileDialog.getSaveFileName(
617+ self.main_window, UiStrings().SaveService, file_path,
618 translate('OpenLP.ServiceManager', 'OpenLP Service Files (*.osz);;'))
619- if not file_name:
620+ if not file_path:
621 return False
622- if os.path.splitext(file_name)[1] == '':
623- file_name += '.osz'
624- else:
625- ext = os.path.splitext(file_name)[1]
626- file_name.replace(ext, '.osz')
627- self.set_file_name(file_name)
628+ file_path.with_suffix('.osz')
629+ self.set_file_name(file_path)
630 self.decide_save_method()
631
632 def decide_save_method(self, field=None):
633@@ -772,7 +772,7 @@
634 return
635 file_to.close()
636 self.new_file()
637- self.set_file_name(file_name)
638+ self.set_file_name(str_to_path(file_name))
639 self.main_window.display_progress_bar(len(items))
640 self.process_service_items(items)
641 delete_file(Path(p_file))
642
643=== modified file 'openlp/core/ui/thememanager.py'
644--- openlp/core/ui/thememanager.py 2017-08-26 16:50:54 +0000
645+++ openlp/core/ui/thememanager.py 2017-09-21 08:29:37 +0000
646@@ -483,7 +483,7 @@
647 name = text_name
648 thumb = os.path.join(self.thumb_path, '{name}.png'.format(name=text_name))
649 item_name = QtWidgets.QListWidgetItem(name)
650- if validate_thumb(theme, thumb):
651+ if validate_thumb(Path(theme), Path(thumb)):
652 icon = build_icon(thumb)
653 else:
654 icon = create_thumb(theme, thumb)
655
656=== modified file 'openlp/plugins/images/lib/mediaitem.py'
657--- openlp/plugins/images/lib/mediaitem.py 2017-08-26 15:06:11 +0000
658+++ openlp/plugins/images/lib/mediaitem.py 2017-09-21 08:29:37 +0000
659@@ -360,7 +360,7 @@
660 if not os.path.exists(image_file.filename):
661 icon = build_icon(':/general/general_delete.png')
662 else:
663- if validate_thumb(image_file.filename, thumb):
664+ if validate_thumb(Path(image_file.filename), Path(thumb)):
665 icon = build_icon(thumb)
666 else:
667 icon = create_thumb(image_file.filename, thumb)
668
669=== modified file 'openlp/plugins/presentations/lib/impresscontroller.py'
670--- openlp/plugins/presentations/lib/impresscontroller.py 2017-08-25 20:03:25 +0000
671+++ openlp/plugins/presentations/lib/impresscontroller.py 2017-09-21 08:29:37 +0000
672@@ -32,11 +32,14 @@
673 # http://nxsy.org/comparing-documents-with-openoffice-and-python
674
675 import logging
676-import os
677 import time
678
679-from openlp.core.common import is_win, Registry, delete_file
680-from openlp.core.common.path import Path
681+from PyQt5 import QtCore
682+
683+from openlp.core.common import Registry, delete_file, get_uno_command, get_uno_instance, is_win
684+from openlp.core.lib import ScreenList
685+from openlp.plugins.presentations.lib.presentationcontroller import PresentationController, PresentationDocument, \
686+ TextType
687
688 if is_win():
689 from win32com.client import Dispatch
690@@ -55,14 +58,6 @@
691 except ImportError:
692 uno_available = False
693
694-from PyQt5 import QtCore
695-
696-from openlp.core.lib import ScreenList
697-from openlp.core.common import get_uno_command, get_uno_instance
698-from openlp.plugins.presentations.lib.presentationcontroller import PresentationController, PresentationDocument, \
699- TextType
700-
701-
702 log = logging.getLogger(__name__)
703
704
705@@ -203,12 +198,15 @@
706 Class which holds information and controls a single presentation.
707 """
708
709- def __init__(self, controller, presentation):
710+ def __init__(self, controller, document_path):
711 """
712 Constructor, store information about the file and initialise.
713+
714+ :param openlp.core.common.path.Path document_path: File path for the document to load
715+ :rtype: None
716 """
717 log.debug('Init Presentation OpenOffice')
718- super(ImpressDocument, self).__init__(controller, presentation)
719+ super().__init__(controller, document_path)
720 self.document = None
721 self.presentation = None
722 self.control = None
723@@ -225,10 +223,9 @@
724 if desktop is None:
725 self.controller.start_process()
726 desktop = self.controller.get_com_desktop()
727- url = 'file:///' + self.file_path.replace('\\', '/').replace(':', '|').replace(' ', '%20')
728 else:
729 desktop = self.controller.get_uno_desktop()
730- url = uno.systemPathToFileUrl(self.file_path)
731+ url = self.file_path.as_uri()
732 if desktop is None:
733 return False
734 self.desktop = desktop
735@@ -254,11 +251,8 @@
736 log.debug('create thumbnails OpenOffice')
737 if self.check_thumbnails():
738 return
739- if is_win():
740- thumb_dir_url = 'file:///' + self.get_temp_folder().replace('\\', '/') \
741- .replace(':', '|').replace(' ', '%20')
742- else:
743- thumb_dir_url = uno.systemPathToFileUrl(self.get_temp_folder())
744+ temp_folder_path = self.get_temp_folder()
745+ thumb_dir_url = temp_folder_path.as_uri()
746 properties = []
747 properties.append(self.create_property('FilterName', 'impress_png_Export'))
748 properties = tuple(properties)
749@@ -266,17 +260,17 @@
750 pages = doc.getDrawPages()
751 if not pages:
752 return
753- if not os.path.isdir(self.get_temp_folder()):
754- os.makedirs(self.get_temp_folder())
755+ if not temp_folder_path.is_dir():
756+ temp_folder_path.mkdir(parents=True)
757 for index in range(pages.getCount()):
758 page = pages.getByIndex(index)
759 doc.getCurrentController().setCurrentPage(page)
760- url_path = '{path}/{name}.png'.format(path=thumb_dir_url, name=str(index + 1))
761- path = os.path.join(self.get_temp_folder(), str(index + 1) + '.png')
762+ url_path = '{path}/{name:d}.png'.format(path=thumb_dir_url, name=index + 1)
763+ path = temp_folder_path / '{number:d}.png'.format(number=index + 1)
764 try:
765 doc.storeToURL(url_path, properties)
766 self.convert_thumbnail(path, index + 1)
767- delete_file(Path(path))
768+ delete_file(path)
769 except ErrorCodeIOException as exception:
770 log.exception('ERROR! ErrorCodeIOException {error:d}'.format(error=exception.ErrCode))
771 except:
772
773=== modified file 'openlp/plugins/presentations/lib/mediaitem.py'
774--- openlp/plugins/presentations/lib/mediaitem.py 2017-08-26 15:06:11 +0000
775+++ openlp/plugins/presentations/lib/mediaitem.py 2017-09-21 08:29:37 +0000
776@@ -19,15 +19,13 @@
777 # with this program; if not, write to the Free Software Foundation, Inc., 59 #
778 # Temple Place, Suite 330, Boston, MA 02111-1307 USA #
779 ###############################################################################
780-
781 import logging
782-import os
783
784 from PyQt5 import QtCore, QtGui, QtWidgets
785
786 from openlp.core.common import Registry, Settings, UiStrings, translate
787 from openlp.core.common.languagemanager import get_locale_key
788-from openlp.core.common.path import path_to_str
789+from openlp.core.common.path import Path, path_to_str, str_to_path
790 from openlp.core.lib import MediaManagerItem, ItemCapabilities, ServiceItemContext,\
791 build_icon, check_item_selected, create_thumb, validate_thumb
792 from openlp.core.lib.ui import critical_error_message_box, create_horizontal_adjusting_combo_box
793@@ -128,7 +126,7 @@
794 """
795 self.list_view.setIconSize(QtCore.QSize(88, 50))
796 file_paths = Settings().value(self.settings_section + '/presentations files')
797- self.load_list([path_to_str(file) for file in file_paths], initial_load=True)
798+ self.load_list([path_to_str(path) for path in file_paths], initial_load=True)
799 self.populate_display_types()
800
801 def populate_display_types(self):
802@@ -152,54 +150,57 @@
803 else:
804 self.presentation_widget.hide()
805
806- def load_list(self, files, target_group=None, initial_load=False):
807+ def load_list(self, file_paths, target_group=None, initial_load=False):
808 """
809 Add presentations into the media manager. This is called both on initial load of the plugin to populate with
810 existing files, and when the user adds new files via the media manager.
811+
812+ :param list[openlp.core.common.path.Path] file_paths: List of file paths to add to the media manager.
813 """
814- current_list = self.get_file_list()
815- titles = [file_path.name for file_path in current_list]
816+ file_paths = [str_to_path(filename) for filename in file_paths]
817+ current_paths = self.get_file_list()
818+ titles = [file_path.name for file_path in current_paths]
819 self.application.set_busy_cursor()
820 if not initial_load:
821- self.main_window.display_progress_bar(len(files))
822+ self.main_window.display_progress_bar(len(file_paths))
823 # Sort the presentations by its filename considering language specific characters.
824- files.sort(key=lambda filename: get_locale_key(os.path.split(str(filename))[1]))
825- for file in files:
826+ file_paths.sort(key=lambda file_path: get_locale_key(file_path.name))
827+ for file_path in file_paths:
828 if not initial_load:
829 self.main_window.increment_progress_bar()
830- if current_list.count(file) > 0:
831+ if current_paths.count(file_path) > 0:
832 continue
833- filename = os.path.split(file)[1]
834- if not os.path.exists(file):
835- item_name = QtWidgets.QListWidgetItem(filename)
836+ file_name = file_path.name
837+ if not file_path.exists():
838+ item_name = QtWidgets.QListWidgetItem(file_name)
839 item_name.setIcon(build_icon(ERROR_IMAGE))
840- item_name.setData(QtCore.Qt.UserRole, file)
841- item_name.setToolTip(file)
842+ item_name.setData(QtCore.Qt.UserRole, path_to_str(file_path))
843+ item_name.setToolTip(str(file_path))
844 self.list_view.addItem(item_name)
845 else:
846- if titles.count(filename) > 0:
847+ if titles.count(file_name) > 0:
848 if not initial_load:
849 critical_error_message_box(translate('PresentationPlugin.MediaItem', 'File Exists'),
850 translate('PresentationPlugin.MediaItem',
851 'A presentation with that filename already exists.'))
852 continue
853- controller_name = self.find_controller_by_type(filename)
854+ controller_name = self.find_controller_by_type(file_path)
855 if controller_name:
856 controller = self.controllers[controller_name]
857- doc = controller.add_document(file)
858- thumb = os.path.join(doc.get_thumbnail_folder(), 'icon.png')
859- preview = doc.get_thumbnail_path(1, True)
860- if not preview and not initial_load:
861+ doc = controller.add_document(file_path)
862+ thumbnail_path = doc.get_thumbnail_folder() / 'icon.png'
863+ preview_path = doc.get_thumbnail_path(1, True)
864+ if not preview_path and not initial_load:
865 doc.load_presentation()
866- preview = doc.get_thumbnail_path(1, True)
867+ preview_path = doc.get_thumbnail_path(1, True)
868 doc.close_presentation()
869- if not (preview and os.path.exists(preview)):
870+ if not (preview_path and preview_path.exists()):
871 icon = build_icon(':/general/general_delete.png')
872 else:
873- if validate_thumb(preview, thumb):
874- icon = build_icon(thumb)
875+ if validate_thumb(Path(preview_path), Path(thumbnail_path)):
876+ icon = build_icon(thumbnail_path)
877 else:
878- icon = create_thumb(preview, thumb)
879+ icon = create_thumb(str(preview_path), str(thumbnail_path))
880 else:
881 if initial_load:
882 icon = build_icon(':/general/general_delete.png')
883@@ -208,10 +209,10 @@
884 translate('PresentationPlugin.MediaItem',
885 'This type of presentation is not supported.'))
886 continue
887- item_name = QtWidgets.QListWidgetItem(filename)
888- item_name.setData(QtCore.Qt.UserRole, file)
889+ item_name = QtWidgets.QListWidgetItem(file_name)
890+ item_name.setData(QtCore.Qt.UserRole, path_to_str(file_path))
891 item_name.setIcon(icon)
892- item_name.setToolTip(file)
893+ item_name.setToolTip(str(file_path))
894 self.list_view.addItem(item_name)
895 if not initial_load:
896 self.main_window.finished_progress_bar()
897@@ -228,8 +229,8 @@
898 self.application.set_busy_cursor()
899 self.main_window.display_progress_bar(len(row_list))
900 for item in items:
901- filepath = str(item.data(QtCore.Qt.UserRole))
902- self.clean_up_thumbnails(filepath)
903+ file_path = str_to_path(item.data(QtCore.Qt.UserRole))
904+ self.clean_up_thumbnails(file_path)
905 self.main_window.increment_progress_bar()
906 self.main_window.finished_progress_bar()
907 for row in row_list:
908@@ -237,30 +238,29 @@
909 Settings().setValue(self.settings_section + '/presentations files', self.get_file_list())
910 self.application.set_normal_cursor()
911
912- def clean_up_thumbnails(self, filepath, clean_for_update=False):
913+ def clean_up_thumbnails(self, file_path, clean_for_update=False):
914 """
915 Clean up the files created such as thumbnails
916
917- :param filepath: File path of the presention to clean up after
918- :param clean_for_update: Only clean thumbnails if update is needed
919- :return: None
920+ :param openlp.core.common.path.Path file_path: File path of the presention to clean up after
921+ :param bool clean_for_update: Only clean thumbnails if update is needed
922+ :rtype: None
923 """
924 for cidx in self.controllers:
925- root, file_ext = os.path.splitext(filepath)
926- file_ext = file_ext[1:]
927+ file_ext = file_path.suffix[1:]
928 if file_ext in self.controllers[cidx].supports or file_ext in self.controllers[cidx].also_supports:
929- doc = self.controllers[cidx].add_document(filepath)
930+ doc = self.controllers[cidx].add_document(file_path)
931 if clean_for_update:
932 thumb_path = doc.get_thumbnail_path(1, True)
933- if not thumb_path or not os.path.exists(filepath) or os.path.getmtime(
934- thumb_path) < os.path.getmtime(filepath):
935+ if not thumb_path or not file_path.exists() or \
936+ thumb_path.stat().st_mtime < file_path.stat().st_mtime:
937 doc.presentation_deleted()
938 else:
939 doc.presentation_deleted()
940 doc.close_presentation()
941
942 def generate_slide_data(self, service_item, item=None, xml_version=False, remote=False,
943- context=ServiceItemContext.Service, presentation_file=None):
944+ context=ServiceItemContext.Service, file_path=None):
945 """
946 Generate the slide data. Needs to be implemented by the plugin.
947
948@@ -276,10 +276,9 @@
949 items = self.list_view.selectedItems()
950 if len(items) > 1:
951 return False
952- filename = presentation_file
953- if filename is None:
954- filename = items[0].data(QtCore.Qt.UserRole)
955- file_type = os.path.splitext(filename.lower())[1][1:]
956+ if file_path is None:
957+ file_path = str_to_path(items[0].data(QtCore.Qt.UserRole))
958+ file_type = file_path.suffix.lower()[1:]
959 if not self.display_type_combo_box.currentText():
960 return False
961 service_item.add_capability(ItemCapabilities.CanEditTitle)
962@@ -292,29 +291,28 @@
963 # force a nonexistent theme
964 service_item.theme = -1
965 for bitem in items:
966- filename = presentation_file
967- if filename is None:
968- filename = bitem.data(QtCore.Qt.UserRole)
969- (path, name) = os.path.split(filename)
970- service_item.title = name
971- if os.path.exists(filename):
972- processor = self.find_controller_by_type(filename)
973+ if file_path is None:
974+ file_path = str_to_path(bitem.data(QtCore.Qt.UserRole))
975+ path, file_name = file_path.parent, file_path.name
976+ service_item.title = file_name
977+ if file_path.exists():
978+ processor = self.find_controller_by_type(file_path)
979 if not processor:
980 return False
981 controller = self.controllers[processor]
982 service_item.processor = None
983- doc = controller.add_document(filename)
984- if doc.get_thumbnail_path(1, True) is None or not os.path.isfile(
985- os.path.join(doc.get_temp_folder(), 'mainslide001.png')):
986+ doc = controller.add_document(file_path)
987+ if doc.get_thumbnail_path(1, True) is None or \
988+ not (doc.get_temp_folder() / 'mainslide001.png').is_file():
989 doc.load_presentation()
990 i = 1
991- image = os.path.join(doc.get_temp_folder(), 'mainslide{number:0>3d}.png'.format(number=i))
992- thumbnail = os.path.join(doc.get_thumbnail_folder(), 'slide%d.png' % i)
993- while os.path.isfile(image):
994- service_item.add_from_image(image, name, thumbnail=thumbnail)
995+ image_path = doc.get_temp_folder() / 'mainslide{number:0>3d}.png'.format(number=i)
996+ thumbnail_path = doc.get_thumbnail_folder() / 'slide{number:d}.png'.format(number=i)
997+ while image_path.is_file():
998+ service_item.add_from_image(str(image_path), file_name, thumbnail=str(thumbnail_path))
999 i += 1
1000- image = os.path.join(doc.get_temp_folder(), 'mainslide{number:0>3d}.png'.format(number=i))
1001- thumbnail = os.path.join(doc.get_thumbnail_folder(), 'slide{number:d}.png'.format(number=i))
1002+ image_path = doc.get_temp_folder() / 'mainslide{number:0>3d}.png'.format(number=i)
1003+ thumbnail_path = doc.get_thumbnail_folder() / 'slide{number:d}.png'.format(number=i)
1004 service_item.add_capability(ItemCapabilities.HasThumbnails)
1005 doc.close_presentation()
1006 return True
1007@@ -324,34 +322,34 @@
1008 critical_error_message_box(translate('PresentationPlugin.MediaItem', 'Missing Presentation'),
1009 translate('PresentationPlugin.MediaItem',
1010 'The presentation {name} no longer exists.'
1011- ).format(name=filename))
1012+ ).format(name=file_path))
1013 return False
1014 else:
1015 service_item.processor = self.display_type_combo_box.currentText()
1016 service_item.add_capability(ItemCapabilities.ProvidesOwnDisplay)
1017 for bitem in items:
1018- filename = bitem.data(QtCore.Qt.UserRole)
1019- (path, name) = os.path.split(filename)
1020- service_item.title = name
1021- if os.path.exists(filename):
1022+ file_path = str_to_path(bitem.data(QtCore.Qt.UserRole))
1023+ path, file_name = file_path.parent, file_path.name
1024+ service_item.title = file_name
1025+ if file_path.exists():
1026 if self.display_type_combo_box.itemData(self.display_type_combo_box.currentIndex()) == 'automatic':
1027- service_item.processor = self.find_controller_by_type(filename)
1028+ service_item.processor = self.find_controller_by_type(file_path)
1029 if not service_item.processor:
1030 return False
1031 controller = self.controllers[service_item.processor]
1032- doc = controller.add_document(filename)
1033+ doc = controller.add_document(file_path)
1034 if doc.get_thumbnail_path(1, True) is None:
1035 doc.load_presentation()
1036 i = 1
1037- img = doc.get_thumbnail_path(i, True)
1038- if img:
1039+ thumbnail_path = doc.get_thumbnail_path(i, True)
1040+ if thumbnail_path:
1041 # Get titles and notes
1042 titles, notes = doc.get_titles_and_notes()
1043 service_item.add_capability(ItemCapabilities.HasDisplayTitle)
1044 if notes.count('') != len(notes):
1045 service_item.add_capability(ItemCapabilities.HasNotes)
1046 service_item.add_capability(ItemCapabilities.HasThumbnails)
1047- while img:
1048+ while thumbnail_path:
1049 # Use title and note if available
1050 title = ''
1051 if titles and len(titles) >= i:
1052@@ -359,9 +357,9 @@
1053 note = ''
1054 if notes and len(notes) >= i:
1055 note = notes[i - 1]
1056- service_item.add_from_command(path, name, img, title, note)
1057+ service_item.add_from_command(str(path), file_name, str(thumbnail_path), title, note)
1058 i += 1
1059- img = doc.get_thumbnail_path(i, True)
1060+ thumbnail_path = doc.get_thumbnail_path(i, True)
1061 doc.close_presentation()
1062 return True
1063 else:
1064@@ -371,7 +369,7 @@
1065 'Missing Presentation'),
1066 translate('PresentationPlugin.MediaItem',
1067 'The presentation {name} is incomplete, '
1068- 'please reload.').format(name=filename))
1069+ 'please reload.').format(name=file_path))
1070 return False
1071 else:
1072 # File is no longer present
1073@@ -379,18 +377,20 @@
1074 critical_error_message_box(translate('PresentationPlugin.MediaItem', 'Missing Presentation'),
1075 translate('PresentationPlugin.MediaItem',
1076 'The presentation {name} no longer exists.'
1077- ).format(name=filename))
1078+ ).format(name=file_path))
1079 return False
1080
1081- def find_controller_by_type(self, filename):
1082+ def find_controller_by_type(self, file_path):
1083 """
1084 Determine the default application controller to use for the selected file type. This is used if "Automatic" is
1085 set as the preferred controller. Find the first (alphabetic) enabled controller which "supports" the extension.
1086 If none found, then look for a controller which "also supports" it instead.
1087
1088- :param filename: The file name
1089+ :param openlp.core.common.path.Path file_path: The file path
1090+ :return: The default application controller for this file type, or None if not supported
1091+ :rtype: PresentationController
1092 """
1093- file_type = os.path.splitext(filename)[1][1:]
1094+ file_type = file_path.suffix[1:]
1095 if not file_type:
1096 return None
1097 for controller in self.controllers:
1098
1099=== modified file 'openlp/plugins/presentations/lib/messagelistener.py'
1100--- openlp/plugins/presentations/lib/messagelistener.py 2016-12-31 11:01:36 +0000
1101+++ openlp/plugins/presentations/lib/messagelistener.py 2017-09-21 08:29:37 +0000
1102@@ -19,16 +19,15 @@
1103 # with this program; if not, write to the Free Software Foundation, Inc., 59 #
1104 # Temple Place, Suite 330, Boston, MA 02111-1307 USA #
1105 ###############################################################################
1106-
1107+import copy
1108 import logging
1109-import copy
1110-import os
1111
1112 from PyQt5 import QtCore
1113
1114 from openlp.core.common import Registry, Settings
1115+from openlp.core.common.path import Path
1116+from openlp.core.lib import ServiceItemContext
1117 from openlp.core.ui import HideMode
1118-from openlp.core.lib import ServiceItemContext
1119 from openlp.plugins.presentations.lib.pdfcontroller import PDF_CONTROLLER_FILETYPES
1120
1121 log = logging.getLogger(__name__)
1122@@ -325,21 +324,25 @@
1123 is_live = message[1]
1124 item = message[0]
1125 hide_mode = message[2]
1126- file = item.get_frame_path()
1127+ file_path = Path(item.get_frame_path())
1128 self.handler = item.processor
1129 # When starting presentation from the servicemanager we convert
1130 # PDF/XPS/OXPS-serviceitems into image-serviceitems. When started from the mediamanager
1131 # the conversion has already been done at this point.
1132- file_type = os.path.splitext(file.lower())[1][1:]
1133+ file_type = file_path.suffix.lower()[1:]
1134 if file_type in PDF_CONTROLLER_FILETYPES:
1135- log.debug('Converting from pdf/xps/oxps to images for serviceitem with file {name}'.format(name=file))
1136+ log.debug('Converting from pdf/xps/oxps to images for serviceitem with file {name}'.format(name=file_path))
1137 # Create a copy of the original item, and then clear the original item so it can be filled with images
1138 item_cpy = copy.copy(item)
1139 item.__init__(None)
1140 if is_live:
1141- self.media_item.generate_slide_data(item, item_cpy, False, False, ServiceItemContext.Live, file)
1142+ # TODO: To Path object
1143+ self.media_item.generate_slide_data(item, item_cpy, False, False, ServiceItemContext.Live,
1144+ str(file_path))
1145 else:
1146- self.media_item.generate_slide_data(item, item_cpy, False, False, ServiceItemContext.Preview, file)
1147+ # TODO: To Path object
1148+ self.media_item.generate_slide_data(item, item_cpy, False, False, ServiceItemContext.Preview,
1149+ str(file_path))
1150 # Some of the original serviceitem attributes is needed in the new serviceitem
1151 item.footer = item_cpy.footer
1152 item.from_service = item_cpy.from_service
1153@@ -352,13 +355,13 @@
1154 self.handler = None
1155 else:
1156 if self.handler == self.media_item.automatic:
1157- self.handler = self.media_item.find_controller_by_type(file)
1158+ self.handler = self.media_item.find_controller_by_type(file_path)
1159 if not self.handler:
1160 return
1161 else:
1162- # the saved handler is not present so need to use one based on file suffix.
1163+ # the saved handler is not present so need to use one based on file_path suffix.
1164 if not self.controllers[self.handler].available:
1165- self.handler = self.media_item.find_controller_by_type(file)
1166+ self.handler = self.media_item.find_controller_by_type(file_path)
1167 if not self.handler:
1168 return
1169 if is_live:
1170@@ -370,7 +373,7 @@
1171 if self.handler is None:
1172 self.controller = controller
1173 else:
1174- controller.add_handler(self.controllers[self.handler], file, hide_mode, message[3])
1175+ controller.add_handler(self.controllers[self.handler], file_path, hide_mode, message[3])
1176 self.timer.start()
1177
1178 def slide(self, message):
1179
1180=== modified file 'openlp/plugins/presentations/lib/pdfcontroller.py'
1181--- openlp/plugins/presentations/lib/pdfcontroller.py 2017-08-26 15:06:11 +0000
1182+++ openlp/plugins/presentations/lib/pdfcontroller.py 2017-09-21 08:29:37 +0000
1183@@ -23,12 +23,11 @@
1184 import os
1185 import logging
1186 import re
1187-from shutil import which
1188 from subprocess import check_output, CalledProcessError
1189
1190 from openlp.core.common import AppLocation, check_binary_exists
1191 from openlp.core.common import Settings, is_win
1192-from openlp.core.common.path import Path, path_to_str
1193+from openlp.core.common.path import which
1194 from openlp.core.lib import ScreenList
1195 from openlp.plugins.presentations.lib.presentationcontroller import PresentationController, PresentationDocument
1196
1197@@ -66,11 +65,12 @@
1198 Function that checks whether a binary is either ghostscript or mudraw or neither.
1199 Is also used from presentationtab.py
1200
1201- :param program_path:The full path to the binary to check.
1202+ :param openlp.core.common.path.Path program_path: The full path to the binary to check.
1203 :return: Type of the binary, 'gs' if ghostscript, 'mudraw' if mudraw, None if invalid.
1204+ :rtype: str | None
1205 """
1206 program_type = None
1207- runlog = check_binary_exists(Path(program_path))
1208+ runlog = check_binary_exists(program_path)
1209 # Analyse the output to see it the program is mudraw, ghostscript or neither
1210 for line in runlog.splitlines():
1211 decoded_line = line.decode()
1212@@ -107,30 +107,29 @@
1213 :return: True if program to open PDF-files was found, otherwise False.
1214 """
1215 log.debug('check_installed Pdf')
1216- self.mudrawbin = ''
1217- self.mutoolbin = ''
1218- self.gsbin = ''
1219+ self.mudrawbin = None
1220+ self.mutoolbin = None
1221+ self.gsbin = None
1222 self.also_supports = []
1223 # Use the user defined program if given
1224 if Settings().value('presentations/enable_pdf_program'):
1225- pdf_program = path_to_str(Settings().value('presentations/pdf_program'))
1226- program_type = self.process_check_binary(pdf_program)
1227+ program_path = Settings().value('presentations/pdf_program')
1228+ program_type = self.process_check_binary(program_path)
1229 if program_type == 'gs':
1230- self.gsbin = pdf_program
1231+ self.gsbin = program_path
1232 elif program_type == 'mudraw':
1233- self.mudrawbin = pdf_program
1234+ self.mudrawbin = program_path
1235 elif program_type == 'mutool':
1236- self.mutoolbin = pdf_program
1237+ self.mutoolbin = program_path
1238 else:
1239 # Fallback to autodetection
1240- application_path = str(AppLocation.get_directory(AppLocation.AppDir))
1241+ application_path = AppLocation.get_directory(AppLocation.AppDir)
1242 if is_win():
1243 # for windows we only accept mudraw.exe or mutool.exe in the base folder
1244- application_path = str(AppLocation.get_directory(AppLocation.AppDir))
1245- if os.path.isfile(os.path.join(application_path, 'mudraw.exe')):
1246- self.mudrawbin = os.path.join(application_path, 'mudraw.exe')
1247- elif os.path.isfile(os.path.join(application_path, 'mutool.exe')):
1248- self.mutoolbin = os.path.join(application_path, 'mutool.exe')
1249+ if (application_path / 'mudraw.exe').is_file():
1250+ self.mudrawbin = application_path / 'mudraw.exe'
1251+ elif (application_path / 'mutool.exe').is_file():
1252+ self.mutoolbin = application_path / 'mutool.exe'
1253 else:
1254 DEVNULL = open(os.devnull, 'wb')
1255 # First try to find mudraw
1256@@ -143,11 +142,11 @@
1257 self.gsbin = which('gs')
1258 # Last option: check if mudraw or mutool is placed in OpenLP base folder
1259 if not self.mudrawbin and not self.mutoolbin and not self.gsbin:
1260- application_path = str(AppLocation.get_directory(AppLocation.AppDir))
1261- if os.path.isfile(os.path.join(application_path, 'mudraw')):
1262- self.mudrawbin = os.path.join(application_path, 'mudraw')
1263- elif os.path.isfile(os.path.join(application_path, 'mutool')):
1264- self.mutoolbin = os.path.join(application_path, 'mutool')
1265+ application_path = AppLocation.get_directory(AppLocation.AppDir)
1266+ if (application_path / 'mudraw').is_file():
1267+ self.mudrawbin = application_path / 'mudraw'
1268+ elif (application_path / 'mutool').is_file():
1269+ self.mutoolbin = application_path / 'mutool'
1270 if self.mudrawbin or self.mutoolbin:
1271 self.also_supports = ['xps', 'oxps']
1272 return True
1273@@ -172,12 +171,15 @@
1274 image-serviceitem on the fly and present as such. Therefore some of the 'playback'
1275 functions is not implemented.
1276 """
1277- def __init__(self, controller, presentation):
1278+ def __init__(self, controller, document_path):
1279 """
1280 Constructor, store information about the file and initialise.
1281+
1282+ :param openlp.core.common.path.Path document_path: Path to the document to load
1283+ :rtype: None
1284 """
1285 log.debug('Init Presentation Pdf')
1286- PresentationDocument.__init__(self, controller, presentation)
1287+ super().__init__(controller, document_path)
1288 self.presentation = None
1289 self.blanked = False
1290 self.hidden = False
1291@@ -200,13 +202,13 @@
1292 :return: The resolution dpi to be used.
1293 """
1294 # Use a postscript script to get size of the pdf. It is assumed that all pages have same size
1295- gs_resolution_script = str(AppLocation.get_directory(
1296- AppLocation.PluginsDir)) + '/presentations/lib/ghostscript_get_resolution.ps'
1297+ gs_resolution_script = AppLocation.get_directory(
1298+ AppLocation.PluginsDir) / 'presentations' / 'lib' / 'ghostscript_get_resolution.ps'
1299 # Run the script on the pdf to get the size
1300 runlog = []
1301 try:
1302- runlog = check_output([self.controller.gsbin, '-dNOPAUSE', '-dNODISPLAY', '-dBATCH',
1303- '-sFile=' + self.file_path, gs_resolution_script],
1304+ runlog = check_output([str(self.controller.gsbin), '-dNOPAUSE', '-dNODISPLAY', '-dBATCH',
1305+ '-sFile={file_path}'.format(file_path=self.file_path), str(gs_resolution_script)],
1306 startupinfo=self.startupinfo)
1307 except CalledProcessError as e:
1308 log.debug(' '.join(e.cmd))
1309@@ -240,46 +242,47 @@
1310 :return: True is loading succeeded, otherwise False.
1311 """
1312 log.debug('load_presentation pdf')
1313+ temp_dir_path = self.get_temp_folder()
1314 # Check if the images has already been created, and if yes load them
1315- if os.path.isfile(os.path.join(self.get_temp_folder(), 'mainslide001.png')):
1316- created_files = sorted(os.listdir(self.get_temp_folder()))
1317- for fn in created_files:
1318- if os.path.isfile(os.path.join(self.get_temp_folder(), fn)):
1319- self.image_files.append(os.path.join(self.get_temp_folder(), fn))
1320+ if (temp_dir_path / 'mainslide001.png').is_file():
1321+ created_files = sorted(temp_dir_path.glob('*'))
1322+ for image_path in created_files:
1323+ if image_path.is_file():
1324+ self.image_files.append(image_path)
1325 self.num_pages = len(self.image_files)
1326 return True
1327 size = ScreenList().current['size']
1328 # Generate images from PDF that will fit the frame.
1329 runlog = ''
1330 try:
1331- if not os.path.isdir(self.get_temp_folder()):
1332- os.makedirs(self.get_temp_folder())
1333+ if not temp_dir_path.is_dir():
1334+ temp_dir_path.mkdir(parents=True)
1335 # The %03d in the file name is handled by each binary
1336 if self.controller.mudrawbin:
1337 log.debug('loading presentation using mudraw')
1338- runlog = check_output([self.controller.mudrawbin, '-w', str(size.width()), '-h', str(size.height()),
1339- '-o', os.path.join(self.get_temp_folder(), 'mainslide%03d.png'), self.file_path],
1340+ runlog = check_output([str(self.controller.mudrawbin), '-w', str(size.width()),
1341+ '-h', str(size.height()),
1342+ '-o', str(temp_dir_path / 'mainslide%03d.png'), str(self.file_path)],
1343 startupinfo=self.startupinfo)
1344 elif self.controller.mutoolbin:
1345 log.debug('loading presentation using mutool')
1346- runlog = check_output([self.controller.mutoolbin, 'draw', '-w', str(size.width()), '-h',
1347- str(size.height()),
1348- '-o', os.path.join(self.get_temp_folder(), 'mainslide%03d.png'), self.file_path],
1349+ runlog = check_output([str(self.controller.mutoolbin), 'draw', '-w', str(size.width()),
1350+ '-h', str(size.height()), '-o', str(temp_dir_path / 'mainslide%03d.png'),
1351+ str(self.file_path)],
1352 startupinfo=self.startupinfo)
1353 elif self.controller.gsbin:
1354 log.debug('loading presentation using gs')
1355 resolution = self.gs_get_resolution(size)
1356- runlog = check_output([self.controller.gsbin, '-dSAFER', '-dNOPAUSE', '-dBATCH', '-sDEVICE=png16m',
1357- '-r' + str(resolution), '-dTextAlphaBits=4', '-dGraphicsAlphaBits=4',
1358- '-sOutputFile=' + os.path.join(self.get_temp_folder(), 'mainslide%03d.png'),
1359- self.file_path], startupinfo=self.startupinfo)
1360- created_files = sorted(os.listdir(self.get_temp_folder()))
1361- for fn in created_files:
1362- if os.path.isfile(os.path.join(self.get_temp_folder(), fn)):
1363- self.image_files.append(os.path.join(self.get_temp_folder(), fn))
1364+ runlog = check_output([str(self.controller.gsbin), '-dSAFER', '-dNOPAUSE', '-dBATCH', '-sDEVICE=png16m',
1365+ '-r{res}'.format(res=resolution), '-dTextAlphaBits=4', '-dGraphicsAlphaBits=4',
1366+ '-sOutputFile={output}'.format(output=temp_dir_path / 'mainslide%03d.png'),
1367+ str(self.file_path)], startupinfo=self.startupinfo)
1368+ created_files = sorted(temp_dir_path.glob('*'))
1369+ for image_path in created_files:
1370+ if image_path.is_file():
1371+ self.image_files.append(image_path)
1372 except Exception as e:
1373- log.debug(e)
1374- log.debug(runlog)
1375+ log.exception(runlog)
1376 return False
1377 self.num_pages = len(self.image_files)
1378 # Create thumbnails
1379
1380=== modified file 'openlp/plugins/presentations/lib/powerpointcontroller.py'
1381--- openlp/plugins/presentations/lib/powerpointcontroller.py 2017-07-04 23:13:51 +0000
1382+++ openlp/plugins/presentations/lib/powerpointcontroller.py 2017-09-21 08:29:37 +0000
1383@@ -120,15 +120,16 @@
1384 Class which holds information and controls a single presentation.
1385 """
1386
1387- def __init__(self, controller, presentation):
1388+ def __init__(self, controller, document_path):
1389 """
1390 Constructor, store information about the file and initialise.
1391
1392 :param controller:
1393- :param presentation:
1394+ :param openlp.core.common.path.Path document_path: Path to the document to load
1395+ :rtype: None
1396 """
1397 log.debug('Init Presentation Powerpoint')
1398- super(PowerpointDocument, self).__init__(controller, presentation)
1399+ super().__init__(controller, document_path)
1400 self.presentation = None
1401 self.index_map = {}
1402 self.slide_count = 0
1403@@ -145,7 +146,7 @@
1404 try:
1405 if not self.controller.process:
1406 self.controller.start_process()
1407- self.controller.process.Presentations.Open(os.path.normpath(self.file_path), False, False, False)
1408+ self.controller.process.Presentations.Open(str(self.file_path), False, False, False)
1409 self.presentation = self.controller.process.Presentations(self.controller.process.Presentations.Count)
1410 self.create_thumbnails()
1411 self.create_titles_and_notes()
1412@@ -177,7 +178,7 @@
1413 if not self.presentation.Slides(num + 1).SlideShowTransition.Hidden:
1414 self.index_map[key] = num + 1
1415 self.presentation.Slides(num + 1).Export(
1416- os.path.join(self.get_thumbnail_folder(), 'slide{key:d}.png'.format(key=key)), 'png', 320, 240)
1417+ str(self.get_thumbnail_folder() / 'slide{key:d}.png'.format(key=key)), 'png', 320, 240)
1418 key += 1
1419 self.slide_count = key - 1
1420
1421@@ -363,9 +364,8 @@
1422 width=size.width(),
1423 horizontal=(right - left)))
1424 log.debug('window title: {title}'.format(title=window_title))
1425- filename_root, filename_ext = os.path.splitext(os.path.basename(self.file_path))
1426 if size.y() == top and size.height() == (bottom - top) and size.x() == left and \
1427- size.width() == (right - left) and filename_root in window_title:
1428+ size.width() == (right - left) and self.file_path.stem in window_title:
1429 log.debug('Found a match and will save the handle')
1430 self.presentation_hwnd = hwnd
1431 # Stop powerpoint from flashing in the taskbar
1432
1433=== modified file 'openlp/plugins/presentations/lib/pptviewcontroller.py'
1434--- openlp/plugins/presentations/lib/pptviewcontroller.py 2017-08-01 20:59:41 +0000
1435+++ openlp/plugins/presentations/lib/pptviewcontroller.py 2017-09-21 08:29:37 +0000
1436@@ -85,9 +85,9 @@
1437 if self.process:
1438 return
1439 log.debug('start PPTView')
1440- dll_path = os.path.join(str(AppLocation.get_directory(AppLocation.AppDir)),
1441- 'plugins', 'presentations', 'lib', 'pptviewlib', 'pptviewlib.dll')
1442- self.process = cdll.LoadLibrary(dll_path)
1443+ dll_path = AppLocation.get_directory(AppLocation.AppDir) \
1444+ / 'plugins' / 'presentations' / 'lib' / 'pptviewlib' / 'pptviewlib.dll'
1445+ self.process = cdll.LoadLibrary(str(dll_path))
1446 if log.isEnabledFor(logging.DEBUG):
1447 self.process.SetDebug(1)
1448
1449@@ -104,12 +104,15 @@
1450 """
1451 Class which holds information and controls a single presentation.
1452 """
1453- def __init__(self, controller, presentation):
1454+ def __init__(self, controller, document_path):
1455 """
1456 Constructor, store information about the file and initialise.
1457+
1458+ :param openlp.core.common.path.Path document_path: File path to the document to load
1459+ :rtype: None
1460 """
1461 log.debug('Init Presentation PowerPoint')
1462- super(PptviewDocument, self).__init__(controller, presentation)
1463+ super().__init__(controller, document_path)
1464 self.presentation = None
1465 self.ppt_id = None
1466 self.blanked = False
1467@@ -121,17 +124,16 @@
1468 the background PptView task started earlier.
1469 """
1470 log.debug('LoadPresentation')
1471- temp_folder = self.get_temp_folder()
1472+ temp_path = self.get_temp_folder()
1473 size = ScreenList().current['size']
1474 rect = RECT(size.x(), size.y(), size.right(), size.bottom())
1475- self.file_path = os.path.normpath(self.file_path)
1476- preview_path = os.path.join(temp_folder, 'slide')
1477+ preview_path = temp_path / 'slide'
1478 # Ensure that the paths are null terminated
1479- byte_file_path = self.file_path.encode('utf-16-le') + b'\0'
1480- preview_path = preview_path.encode('utf-16-le') + b'\0'
1481- if not os.path.isdir(temp_folder):
1482- os.makedirs(temp_folder)
1483- self.ppt_id = self.controller.process.OpenPPT(byte_file_path, None, rect, preview_path)
1484+ file_path_utf16 = str(self.file_path).encode('utf-16-le') + b'\0'
1485+ preview_path_utf16 = str(preview_path).encode('utf-16-le') + b'\0'
1486+ if not temp_path.is_dir():
1487+ temp_path.mkdir(parents=True)
1488+ self.ppt_id = self.controller.process.OpenPPT(file_path_utf16, None, rect, preview_path_utf16)
1489 if self.ppt_id >= 0:
1490 self.create_thumbnails()
1491 self.stop_presentation()
1492@@ -148,7 +150,7 @@
1493 return
1494 log.debug('create_thumbnails proceeding')
1495 for idx in range(self.get_slide_count()):
1496- path = '{folder}\\slide{index}.bmp'.format(folder=self.get_temp_folder(), index=str(idx + 1))
1497+ path = self.get_temp_folder() / 'slide{index:d}.bmp'.format(index=idx + 1)
1498 self.convert_thumbnail(path, idx + 1)
1499
1500 def create_titles_and_notes(self):
1501@@ -161,13 +163,12 @@
1502 """
1503 titles = None
1504 notes = None
1505- filename = os.path.normpath(self.file_path)
1506 # let's make sure we have a valid zipped presentation
1507- if os.path.exists(filename) and zipfile.is_zipfile(filename):
1508+ if self.file_path.exists() and zipfile.is_zipfile(str(self.file_path)):
1509 namespaces = {"p": "http://schemas.openxmlformats.org/presentationml/2006/main",
1510 "a": "http://schemas.openxmlformats.org/drawingml/2006/main"}
1511 # open the file
1512- with zipfile.ZipFile(filename) as zip_file:
1513+ with zipfile.ZipFile(str(self.file_path)) as zip_file:
1514 # find the presentation.xml to get the slide count
1515 with zip_file.open('ppt/presentation.xml') as pres:
1516 tree = ElementTree.parse(pres)
1517
1518=== modified file 'openlp/plugins/presentations/lib/presentationcontroller.py'
1519--- openlp/plugins/presentations/lib/presentationcontroller.py 2017-08-25 20:03:25 +0000
1520+++ openlp/plugins/presentations/lib/presentationcontroller.py 2017-09-21 08:29:37 +0000
1521@@ -19,15 +19,12 @@
1522 # with this program; if not, write to the Free Software Foundation, Inc., 59 #
1523 # Temple Place, Suite 330, Boston, MA 02111-1307 USA #
1524 ###############################################################################
1525-
1526 import logging
1527-import os
1528-import shutil
1529
1530 from PyQt5 import QtCore
1531
1532 from openlp.core.common import Registry, AppLocation, Settings, check_directory_exists, md5_hash
1533-from openlp.core.common.path import Path
1534+from openlp.core.common.path import Path, rmtree
1535 from openlp.core.lib import create_thumb, validate_thumb
1536
1537 log = logging.getLogger(__name__)
1538@@ -86,20 +83,27 @@
1539 Returns a path to an image containing a preview for the requested slide
1540
1541 """
1542- def __init__(self, controller, name):
1543+ def __init__(self, controller, document_path):
1544 """
1545 Constructor for the PresentationController class
1546+
1547+ :param controller:
1548+ :param openlp.core.common.path.Path document_path: Path to the document to load.
1549+ :rtype: None
1550 """
1551 self.controller = controller
1552- self._setup(name)
1553+ self._setup(document_path)
1554
1555- def _setup(self, name):
1556+ def _setup(self, document_path):
1557 """
1558 Run some initial setup. This method is separate from __init__ in order to mock it out in tests.
1559+
1560+ :param openlp.core.common.path.Path document_path: Path to the document to load.
1561+ :rtype: None
1562 """
1563 self.slide_number = 0
1564- self.file_path = name
1565- check_directory_exists(Path(self.get_thumbnail_folder()))
1566+ self.file_path = document_path
1567+ check_directory_exists(self.get_thumbnail_folder())
1568
1569 def load_presentation(self):
1570 """
1571@@ -116,49 +120,54 @@
1572 a file, e.g. thumbnails
1573 """
1574 try:
1575- if os.path.exists(self.get_thumbnail_folder()):
1576- shutil.rmtree(self.get_thumbnail_folder())
1577- if os.path.exists(self.get_temp_folder()):
1578- shutil.rmtree(self.get_temp_folder())
1579+ thumbnail_folder_path = self.get_thumbnail_folder()
1580+ temp_folder_path = self.get_temp_folder()
1581+ if thumbnail_folder_path.exists():
1582+ rmtree(thumbnail_folder_path)
1583+ if temp_folder_path.exists():
1584+ rmtree(temp_folder_path)
1585 except OSError:
1586 log.exception('Failed to delete presentation controller files')
1587
1588- def get_file_name(self):
1589- """
1590- Return just the filename of the presentation, without the directory
1591- """
1592- return os.path.split(self.file_path)[1]
1593-
1594 def get_thumbnail_folder(self):
1595 """
1596 The location where thumbnail images will be stored
1597+
1598+ :return: The path to the thumbnail
1599+ :rtype: openlp.core.common.path.Path
1600 """
1601 # TODO: If statement can be removed when the upgrade path from 2.0.x to 2.2.x is no longer needed
1602 if Settings().value('presentations/thumbnail_scheme') == 'md5':
1603- folder = md5_hash(self.file_path.encode('utf-8'))
1604+ folder = md5_hash(bytes(self.file_path))
1605 else:
1606- folder = self.get_file_name()
1607- return os.path.join(self.controller.thumbnail_folder, folder)
1608+ folder = self.file_path.name
1609+ return Path(self.controller.thumbnail_folder, folder)
1610
1611 def get_temp_folder(self):
1612 """
1613 The location where thumbnail images will be stored
1614+
1615+ :return: The path to the temporary file folder
1616+ :rtype: openlp.core.common.path.Path
1617 """
1618 # TODO: If statement can be removed when the upgrade path from 2.0.x to 2.2.x is no longer needed
1619 if Settings().value('presentations/thumbnail_scheme') == 'md5':
1620- folder = md5_hash(self.file_path.encode('utf-8'))
1621+ folder = md5_hash(bytes(self.file_path))
1622 else:
1623- folder = folder = self.get_file_name()
1624- return os.path.join(self.controller.temp_folder, folder)
1625+ folder = self.file_path.name
1626+ return Path(self.controller.temp_folder, folder)
1627
1628 def check_thumbnails(self):
1629 """
1630- Returns ``True`` if the thumbnail images exist and are more recent than the powerpoint file.
1631+ Check that the last thumbnail image exists and is valid and are more recent than the powerpoint file.
1632+
1633+ :return: If the thumbnail is valid
1634+ :rtype: bool
1635 """
1636- last_image = self.get_thumbnail_path(self.get_slide_count(), True)
1637- if not (last_image and os.path.isfile(last_image)):
1638+ last_image_path = self.get_thumbnail_path(self.get_slide_count(), True)
1639+ if not (last_image_path and last_image_path.is_file()):
1640 return False
1641- return validate_thumb(self.file_path, last_image)
1642+ return validate_thumb(Path(self.file_path), Path(last_image_path))
1643
1644 def close_presentation(self):
1645 """
1646@@ -241,25 +250,31 @@
1647 """
1648 pass
1649
1650- def convert_thumbnail(self, file, idx):
1651+ def convert_thumbnail(self, image_path, index):
1652 """
1653 Convert the slide image the application made to a scaled 360px height .png image.
1654+
1655+ :param openlp.core.common.path.Path image_path: Path to the image to create a thumb nail of
1656+ :param int index: The index of the slide to create the thumbnail for.
1657+ :rtype: None
1658 """
1659 if self.check_thumbnails():
1660 return
1661- if os.path.isfile(file):
1662- thumb_path = self.get_thumbnail_path(idx, False)
1663- create_thumb(file, thumb_path, False, QtCore.QSize(-1, 360))
1664+ if image_path.is_file():
1665+ thumb_path = self.get_thumbnail_path(index, False)
1666+ create_thumb(str(image_path), str(thumb_path), False, QtCore.QSize(-1, 360))
1667
1668- def get_thumbnail_path(self, slide_no, check_exists):
1669+ def get_thumbnail_path(self, slide_no, check_exists=False):
1670 """
1671 Returns an image path containing a preview for the requested slide
1672
1673- :param slide_no: The slide an image is required for, starting at 1
1674- :param check_exists:
1675+ :param int slide_no: The slide an image is required for, starting at 1
1676+ :param bool check_exists: Check if the generated path exists
1677+ :return: The path, or None if the :param:`check_exists` is True and the file does not exist
1678+ :rtype: openlp.core.common.path.Path | None
1679 """
1680- path = os.path.join(self.get_thumbnail_folder(), self.controller.thumbnail_prefix + str(slide_no) + '.png')
1681- if os.path.isfile(path) or not check_exists:
1682+ path = self.get_thumbnail_folder() / (self.controller.thumbnail_prefix + str(slide_no) + '.png')
1683+ if path.is_file() or not check_exists:
1684 return path
1685 else:
1686 return None
1687@@ -302,44 +317,38 @@
1688 Reads the titles from the titles file and
1689 the notes files and returns the content in two lists
1690 """
1691- titles = []
1692 notes = []
1693- titles_file = os.path.join(self.get_thumbnail_folder(), 'titles.txt')
1694- if os.path.exists(titles_file):
1695+ titles_path = self.get_thumbnail_folder() / 'titles.txt'
1696+ try:
1697+ titles = titles_path.read_text().splitlines()
1698+ except:
1699+ log.exception('Failed to open/read existing titles file')
1700+ titles = []
1701+ for slide_no, title in enumerate(titles, 1):
1702+ notes_path = self.get_thumbnail_folder() / 'slideNotes{number:d}.txt'.format(number=slide_no)
1703 try:
1704- with open(titles_file, encoding='utf-8') as fi:
1705- titles = fi.read().splitlines()
1706+ note = notes_path.read_text()
1707 except:
1708- log.exception('Failed to open/read existing titles file')
1709- titles = []
1710- for slide_no, title in enumerate(titles, 1):
1711- notes_file = os.path.join(self.get_thumbnail_folder(), 'slideNotes{number:d}.txt'.format(number=slide_no))
1712- note = ''
1713- if os.path.exists(notes_file):
1714- try:
1715- with open(notes_file, encoding='utf-8') as fn:
1716- note = fn.read()
1717- except:
1718- log.exception('Failed to open/read notes file')
1719- note = ''
1720+ log.exception('Failed to open/read notes file')
1721+ note = ''
1722 notes.append(note)
1723 return titles, notes
1724
1725 def save_titles_and_notes(self, titles, notes):
1726 """
1727- Performs the actual persisting of titles to the titles.txt
1728- and notes to the slideNote%.txt
1729+ Performs the actual persisting of titles to the titles.txt and notes to the slideNote%.txt
1730+
1731+ :param list[str] titles: The titles to save
1732+ :param list[str] notes: The notes to save
1733+ :rtype: None
1734 """
1735 if titles:
1736- titles_file = os.path.join(self.get_thumbnail_folder(), 'titles.txt')
1737- with open(titles_file, mode='wt', encoding='utf-8') as fo:
1738- fo.writelines(titles)
1739+ titles_path = self.get_thumbnail_folder() / 'titles.txt'
1740+ titles_path.write_text('\n'.join(titles))
1741 if notes:
1742 for slide_no, note in enumerate(notes, 1):
1743- notes_file = os.path.join(self.get_thumbnail_folder(),
1744- 'slideNotes{number:d}.txt'.format(number=slide_no))
1745- with open(notes_file, mode='wt', encoding='utf-8') as fn:
1746- fn.write(note)
1747+ notes_path = self.get_thumbnail_folder() / 'slideNotes{number:d}.txt'.format(number=slide_no)
1748+ notes_path.write_text(note)
1749
1750
1751 class PresentationController(object):
1752@@ -416,12 +425,11 @@
1753 self.document_class = document_class
1754 self.settings_section = self.plugin.settings_section
1755 self.available = None
1756- self.temp_folder = os.path.join(str(AppLocation.get_section_data_path(self.settings_section)), name)
1757- self.thumbnail_folder = os.path.join(
1758- str(AppLocation.get_section_data_path(self.settings_section)), 'thumbnails')
1759+ self.temp_folder = AppLocation.get_section_data_path(self.settings_section) / name
1760+ self.thumbnail_folder = AppLocation.get_section_data_path(self.settings_section) / 'thumbnails'
1761 self.thumbnail_prefix = 'slide'
1762- check_directory_exists(Path(self.thumbnail_folder))
1763- check_directory_exists(Path(self.temp_folder))
1764+ check_directory_exists(self.thumbnail_folder)
1765+ check_directory_exists(self.temp_folder)
1766
1767 def enabled(self):
1768 """
1769@@ -456,11 +464,15 @@
1770 log.debug('Kill')
1771 self.close_presentation()
1772
1773- def add_document(self, name):
1774+ def add_document(self, document_path):
1775 """
1776 Called when a new presentation document is opened.
1777+
1778+ :param openlp.core.common.path.Path document_path: Path to the document to load
1779+ :return: The document
1780+ :rtype: PresentationDocument
1781 """
1782- document = self.document_class(self, name)
1783+ document = self.document_class(self, document_path)
1784 self.docs.append(document)
1785 return document
1786
1787
1788=== modified file 'openlp/plugins/presentations/lib/presentationtab.py'
1789--- openlp/plugins/presentations/lib/presentationtab.py 2017-08-26 15:06:11 +0000
1790+++ openlp/plugins/presentations/lib/presentationtab.py 2017-09-21 08:29:37 +0000
1791@@ -38,7 +38,6 @@
1792 """
1793 Constructor
1794 """
1795- self.parent = parent
1796 self.controllers = controllers
1797 super(PresentationTab, self).__init__(parent, title, visible_title, icon_path)
1798 self.activated = False
1799@@ -194,7 +193,7 @@
1800 pdf_program_path = self.program_path_edit.path
1801 enable_pdf_program = self.pdf_program_check_box.checkState()
1802 # If the given program is blank disable using the program
1803- if not pdf_program_path:
1804+ if pdf_program_path is None:
1805 enable_pdf_program = 0
1806 if pdf_program_path != Settings().value(self.settings_section + '/pdf_program'):
1807 Settings().setValue(self.settings_section + '/pdf_program', pdf_program_path)
1808@@ -220,9 +219,11 @@
1809
1810 def on_program_path_edit_path_changed(self, new_path):
1811 """
1812- Select the mudraw or ghostscript binary that should be used.
1813+ Handle the `pathEditChanged` signal from program_path_edit
1814+
1815+ :param openlp.core.common.path.Path new_path: File path to the new program
1816+ :rtype: None
1817 """
1818- new_path = path_to_str(new_path)
1819 if new_path:
1820 if not PdfController.process_check_binary(new_path):
1821 critical_error_message_box(UiStrings().Error,
1822
1823=== modified file 'openlp/plugins/remotes/deploy.py'
1824--- openlp/plugins/remotes/deploy.py 2017-08-12 20:58:16 +0000
1825+++ openlp/plugins/remotes/deploy.py 2017-09-21 08:29:37 +0000
1826@@ -64,6 +64,6 @@
1827 file_size = get_url_file_size('https://get.openlp.org/webclient/site.zip')
1828 callback.setRange(0, file_size)
1829 if url_get_file(callback, '{host}{name}'.format(host='https://get.openlp.org/webclient/', name='site.zip'),
1830- os.path.join(str(AppLocation.get_section_data_path('remotes')), 'site.zip'),
1831+ AppLocation.get_section_data_path('remotes') / 'site.zip',
1832 sha256=sha256):
1833 deploy_zipfile(str(AppLocation.get_section_data_path('remotes')), 'site.zip')
1834
1835=== modified file 'openlp/plugins/songs/reporting.py'
1836--- openlp/plugins/songs/reporting.py 2016-12-31 11:01:36 +0000
1837+++ openlp/plugins/songs/reporting.py 2017-09-21 08:29:37 +0000
1838@@ -25,10 +25,10 @@
1839 import csv
1840 import logging
1841
1842-from PyQt5 import QtWidgets
1843-
1844 from openlp.core.common import Registry, translate
1845+from openlp.core.common.path import Path
1846 from openlp.core.lib.ui import critical_error_message_box
1847+from openlp.core.ui.lib.filedialog import FileDialog
1848 from openlp.plugins.songs.lib.db import Song
1849
1850
1851@@ -42,58 +42,55 @@
1852 """
1853 main_window = Registry().get('main_window')
1854 plugin = Registry().get('songs').plugin
1855- report_file_name, filter_used = QtWidgets.QFileDialog.getSaveFileName(
1856+ report_file_path, filter_used = FileDialog.getSaveFileName(
1857 main_window,
1858 translate('SongPlugin.ReportSongList', 'Save File'),
1859- translate('SongPlugin.ReportSongList', 'song_extract.csv'),
1860+ Path(translate('SongPlugin.ReportSongList', 'song_extract.csv')),
1861 translate('SongPlugin.ReportSongList', 'CSV format (*.csv)'))
1862
1863- if not report_file_name:
1864+ if report_file_path is None:
1865 main_window.error_message(
1866 translate('SongPlugin.ReportSongList', 'Output Path Not Selected'),
1867- translate('SongPlugin.ReportSongList', 'You have not set a valid output location for your '
1868- 'report. \nPlease select an existing path '
1869- 'on your computer.')
1870+ translate('SongPlugin.ReportSongList', 'You have not set a valid output location for your report. \n'
1871+ 'Please select an existing path on your computer.')
1872 )
1873 return
1874- if not report_file_name.endswith('csv'):
1875- report_file_name += '.csv'
1876- file_handle = None
1877+ report_file_path.with_suffix('.csv')
1878 Registry().get('application').set_busy_cursor()
1879 try:
1880- file_handle = open(report_file_name, 'wt')
1881- fieldnames = ('Title', 'Alternative Title', 'Copyright', 'Author(s)', 'Song Book', 'Topic')
1882- writer = csv.DictWriter(file_handle, fieldnames=fieldnames, quoting=csv.QUOTE_ALL)
1883- headers = dict((n, n) for n in fieldnames)
1884- writer.writerow(headers)
1885- song_list = plugin.manager.get_all_objects(Song)
1886- for song in song_list:
1887- author_list = []
1888- for author_song in song.authors_songs:
1889- author_list.append(author_song.author.display_name)
1890- author_string = ' | '.join(author_list)
1891- book_list = []
1892- for book_song in song.songbook_entries:
1893- if hasattr(book_song, 'entry') and book_song.entry:
1894- book_list.append('{name} #{entry}'.format(name=book_song.songbook.name, entry=book_song.entry))
1895- book_string = ' | '.join(book_list)
1896- topic_list = []
1897- for topic_song in song.topics:
1898- if hasattr(topic_song, 'name'):
1899- topic_list.append(topic_song.name)
1900- topic_string = ' | '.join(topic_list)
1901- writer.writerow({'Title': song.title,
1902- 'Alternative Title': song.alternate_title,
1903- 'Copyright': song.copyright,
1904- 'Author(s)': author_string,
1905- 'Song Book': book_string,
1906- 'Topic': topic_string})
1907- Registry().get('application').set_normal_cursor()
1908- main_window.information_message(
1909- translate('SongPlugin.ReportSongList', 'Report Creation'),
1910- translate('SongPlugin.ReportSongList',
1911- 'Report \n{name} \nhas been successfully created. ').format(name=report_file_name)
1912- )
1913+ with report_file_path.open('wt') as file_handle:
1914+ fieldnames = ('Title', 'Alternative Title', 'Copyright', 'Author(s)', 'Song Book', 'Topic')
1915+ writer = csv.DictWriter(file_handle, fieldnames=fieldnames, quoting=csv.QUOTE_ALL)
1916+ headers = dict((n, n) for n in fieldnames)
1917+ writer.writerow(headers)
1918+ song_list = plugin.manager.get_all_objects(Song)
1919+ for song in song_list:
1920+ author_list = []
1921+ for author_song in song.authors_songs:
1922+ author_list.append(author_song.author.display_name)
1923+ author_string = ' | '.join(author_list)
1924+ book_list = []
1925+ for book_song in song.songbook_entries:
1926+ if hasattr(book_song, 'entry') and book_song.entry:
1927+ book_list.append('{name} #{entry}'.format(name=book_song.songbook.name, entry=book_song.entry))
1928+ book_string = ' | '.join(book_list)
1929+ topic_list = []
1930+ for topic_song in song.topics:
1931+ if hasattr(topic_song, 'name'):
1932+ topic_list.append(topic_song.name)
1933+ topic_string = ' | '.join(topic_list)
1934+ writer.writerow({'Title': song.title,
1935+ 'Alternative Title': song.alternate_title,
1936+ 'Copyright': song.copyright,
1937+ 'Author(s)': author_string,
1938+ 'Song Book': book_string,
1939+ 'Topic': topic_string})
1940+ Registry().get('application').set_normal_cursor()
1941+ main_window.information_message(
1942+ translate('SongPlugin.ReportSongList', 'Report Creation'),
1943+ translate('SongPlugin.ReportSongList',
1944+ 'Report \n{name} \nhas been successfully created. ').format(name=report_file_path)
1945+ )
1946 except OSError as ose:
1947 Registry().get('application').set_normal_cursor()
1948 log.exception('Failed to write out song usage records')
1949@@ -101,6 +98,3 @@
1950 translate('SongPlugin.ReportSongList',
1951 'An error occurred while extracting: {error}'
1952 ).format(error=ose.strerror))
1953- finally:
1954- if file_handle:
1955- file_handle.close()
1956
1957=== modified file 'tests/functional/openlp_core_common/test_httputils.py'
1958--- tests/functional/openlp_core_common/test_httputils.py 2017-06-09 20:53:13 +0000
1959+++ tests/functional/openlp_core_common/test_httputils.py 2017-09-21 08:29:37 +0000
1960@@ -29,6 +29,7 @@
1961 from unittest.mock import MagicMock, patch
1962
1963 from openlp.core.common.httputils import get_user_agent, get_web_page, get_url_file_size, url_get_file, ping
1964+from openlp.core.common.path import Path
1965
1966 from tests.helpers.testmixin import TestMixin
1967
1968@@ -267,7 +268,7 @@
1969 mocked_urlopen.side_effect = socket.timeout()
1970
1971 # WHEN: Attempt to retrieve a file
1972- url_get_file(MagicMock(), url='http://localhost/test', f_path=self.tempfile)
1973+ url_get_file(MagicMock(), url='http://localhost/test', f_path=Path(self.tempfile))
1974
1975 # THEN: socket.timeout should have been caught
1976 # NOTE: Test is if $tmpdir/tempfile is still there, then test fails since ftw deletes bad downloaded files
1977
1978=== modified file 'tests/functional/openlp_core_common/test_path.py'
1979--- tests/functional/openlp_core_common/test_path.py 2017-08-25 20:03:25 +0000
1980+++ tests/functional/openlp_core_common/test_path.py 2017-09-21 08:29:37 +0000
1981@@ -24,8 +24,209 @@
1982 """
1983 import os
1984 from unittest import TestCase
1985-
1986-from openlp.core.common.path import Path, path_to_str, str_to_path
1987+from unittest.mock import ANY, MagicMock, patch
1988+
1989+from openlp.core.common.path import Path, copy, copyfile, copytree, path_to_str, replace_params, rmtree, str_to_path, \
1990+ which
1991+
1992+
1993+class TestShutil(TestCase):
1994+ """
1995+ Tests for the :mod:`openlp.core.common.path` module
1996+ """
1997+ def test_replace_params_no_params(self):
1998+ """
1999+ Test replace_params when called with and empty tuple instead of parameters to replace
2000+ """
2001+ # GIVEN: Some test data
2002+ test_args = (1, 2)
2003+ test_kwargs = {'arg3': 3, 'arg4': 4}
2004+ test_params = tuple()
2005+
2006+ # WHEN: Calling replace_params
2007+ result_args, result_kwargs = replace_params(test_args, test_kwargs, test_params)
2008+
2009+ # THEN: The positional and keyword args should not have changed
2010+ self.assertEqual(test_args, result_args)
2011+ self.assertEqual(test_kwargs, result_kwargs)
2012+
2013+ def test_replace_params_params(self):
2014+ """
2015+ Test replace_params when given a positional and a keyword argument to change
2016+ """
2017+ # GIVEN: Some test data
2018+ test_args = (1, 2)
2019+ test_kwargs = {'arg3': 3, 'arg4': 4}
2020+ test_params = ((1, 'arg2', str), (2, 'arg3', str))
2021+
2022+ # WHEN: Calling replace_params
2023+ result_args, result_kwargs = replace_params(test_args, test_kwargs, test_params)
2024+
2025+ # THEN: The positional and keyword args should have have changed
2026+ self.assertEqual(result_args, (1, '2'))
2027+ self.assertEqual(result_kwargs, {'arg3': '3', 'arg4': 4})
2028+
2029+ def test_copy(self):
2030+ """
2031+ Test :func:`openlp.core.common.path.copy`
2032+ """
2033+ # GIVEN: A mocked `shutil.copy` which returns a test path as a string
2034+ with patch('openlp.core.common.path.shutil.copy', return_value=os.path.join('destination', 'test', 'path')) \
2035+ as mocked_shutil_copy:
2036+
2037+ # WHEN: Calling :func:`openlp.core.common.path.copy` with the src and dst parameters as Path object types
2038+ result = copy(Path('source', 'test', 'path'), Path('destination', 'test', 'path'))
2039+
2040+ # THEN: :func:`shutil.copy` should have been called with the str equivalents of the Path objects.
2041+ # :func:`openlp.core.common.path.copy` should return the str type result of calling
2042+ # :func:`shutil.copy` as a Path object.
2043+ mocked_shutil_copy.assert_called_once_with(os.path.join('source', 'test', 'path'),
2044+ os.path.join('destination', 'test', 'path'))
2045+ self.assertEqual(result, Path('destination', 'test', 'path'))
2046+
2047+ def test_copy_follow_optional_params(self):
2048+ """
2049+ Test :func:`openlp.core.common.path.copy` when follow_symlinks is set to false
2050+ """
2051+ # GIVEN: A mocked `shutil.copy`
2052+ with patch('openlp.core.common.path.shutil.copy', return_value='') as mocked_shutil_copy:
2053+
2054+ # WHEN: Calling :func:`openlp.core.common.path.copy` with :param:`follow_symlinks` set to False
2055+ copy(Path('source', 'test', 'path'), Path('destination', 'test', 'path'), follow_symlinks=False)
2056+
2057+ # THEN: :func:`shutil.copy` should have been called with :param:`follow_symlinks` set to false
2058+ mocked_shutil_copy.assert_called_once_with(ANY, ANY, follow_symlinks=False)
2059+
2060+ def test_copyfile(self):
2061+ """
2062+ Test :func:`openlp.core.common.path.copyfile`
2063+ """
2064+ # GIVEN: A mocked :func:`shutil.copyfile` which returns a test path as a string
2065+ with patch('openlp.core.common.path.shutil.copyfile',
2066+ return_value=os.path.join('destination', 'test', 'path')) as mocked_shutil_copyfile:
2067+
2068+ # WHEN: Calling :func:`openlp.core.common.path.copyfile` with the src and dst parameters as Path object
2069+ # types
2070+ result = copyfile(Path('source', 'test', 'path'), Path('destination', 'test', 'path'))
2071+
2072+ # THEN: :func:`shutil.copyfile` should have been called with the str equivalents of the Path objects.
2073+ # :func:`openlp.core.common.path.copyfile` should return the str type result of calling
2074+ # :func:`shutil.copyfile` as a Path object.
2075+ mocked_shutil_copyfile.assert_called_once_with(os.path.join('source', 'test', 'path'),
2076+ os.path.join('destination', 'test', 'path'))
2077+ self.assertEqual(result, Path('destination', 'test', 'path'))
2078+
2079+ def test_copyfile_optional_params(self):
2080+ """
2081+ Test :func:`openlp.core.common.path.copyfile` when follow_symlinks is set to false
2082+ """
2083+ # GIVEN: A mocked :func:`shutil.copyfile`
2084+ with patch('openlp.core.common.path.shutil.copyfile', return_value='') as mocked_shutil_copyfile:
2085+
2086+ # WHEN: Calling :func:`openlp.core.common.path.copyfile` with :param:`follow_symlinks` set to False
2087+ copyfile(Path('source', 'test', 'path'), Path('destination', 'test', 'path'), follow_symlinks=False)
2088+
2089+ # THEN: :func:`shutil.copyfile` should have been called with the optional parameters, with out any of the
2090+ # values being modified
2091+ mocked_shutil_copyfile.assert_called_once_with(ANY, ANY, follow_symlinks=False)
2092+
2093+ def test_copytree(self):
2094+ """
2095+ Test :func:`openlp.core.common.path.copytree`
2096+ """
2097+ # GIVEN: A mocked :func:`shutil.copytree` which returns a test path as a string
2098+ with patch('openlp.core.common.path.shutil.copytree',
2099+ return_value=os.path.join('destination', 'test', 'path')) as mocked_shutil_copytree:
2100+
2101+ # WHEN: Calling :func:`openlp.core.common.path.copytree` with the src and dst parameters as Path object
2102+ # types
2103+ result = copytree(Path('source', 'test', 'path'), Path('destination', 'test', 'path'))
2104+
2105+ # THEN: :func:`shutil.copytree` should have been called with the str equivalents of the Path objects.
2106+ # :func:`openlp.core.common.path.copytree` should return the str type result of calling
2107+ # :func:`shutil.copytree` as a Path object.
2108+ mocked_shutil_copytree.assert_called_once_with(os.path.join('source', 'test', 'path'),
2109+ os.path.join('destination', 'test', 'path'))
2110+ self.assertEqual(result, Path('destination', 'test', 'path'))
2111+
2112+ def test_copytree_optional_params(self):
2113+ """
2114+ Test :func:`openlp.core.common.path.copytree` when optional parameters are passed
2115+ """
2116+ # GIVEN: A mocked :func:`shutil.copytree`
2117+ with patch('openlp.core.common.path.shutil.copytree', return_value='') as mocked_shutil_copytree:
2118+ mocked_ignore = MagicMock()
2119+ mocked_copy_function = MagicMock()
2120+
2121+ # WHEN: Calling :func:`openlp.core.common.path.copytree` with the optional parameters set
2122+ copytree(Path('source', 'test', 'path'), Path('destination', 'test', 'path'), symlinks=True,
2123+ ignore=mocked_ignore, copy_function=mocked_copy_function, ignore_dangling_symlinks=True)
2124+
2125+ # THEN: :func:`shutil.copytree` should have been called with the optional parameters, with out any of the
2126+ # values being modified
2127+ mocked_shutil_copytree.assert_called_once_with(ANY, ANY, symlinks=True, ignore=mocked_ignore,
2128+ copy_function=mocked_copy_function,
2129+ ignore_dangling_symlinks=True)
2130+
2131+ def test_rmtree(self):
2132+ """
2133+ Test :func:`rmtree`
2134+ """
2135+ # GIVEN: A mocked :func:`shutil.rmtree`
2136+ with patch('openlp.core.common.path.shutil.rmtree', return_value=None) as mocked_shutil_rmtree:
2137+
2138+ # WHEN: Calling :func:`openlp.core.common.path.rmtree` with the path parameter as Path object type
2139+ result = rmtree(Path('test', 'path'))
2140+
2141+ # THEN: :func:`shutil.rmtree` should have been called with the str equivalents of the Path object.
2142+ mocked_shutil_rmtree.assert_called_once_with(os.path.join('test', 'path'))
2143+ self.assertIsNone(result)
2144+
2145+ def test_rmtree_optional_params(self):
2146+ """
2147+ Test :func:`openlp.core.common.path.rmtree` when optional parameters are passed
2148+ """
2149+ # GIVEN: A mocked :func:`shutil.rmtree`
2150+ with patch('openlp.core.common.path.shutil.rmtree', return_value='') as mocked_shutil_rmtree:
2151+ mocked_on_error = MagicMock()
2152+
2153+ # WHEN: Calling :func:`openlp.core.common.path.rmtree` with :param:`ignore_errors` set to True and
2154+ # :param:`onerror` set to a mocked object
2155+ rmtree(Path('test', 'path'), ignore_errors=True, onerror=mocked_on_error)
2156+
2157+ # THEN: :func:`shutil.rmtree` should have been called with the optional parameters, with out any of the
2158+ # values being modified
2159+ mocked_shutil_rmtree.assert_called_once_with(ANY, ignore_errors=True, onerror=mocked_on_error)
2160+
2161+ def test_which_no_command(self):
2162+ """
2163+ Test :func:`openlp.core.common.path.which` when the command is not found.
2164+ """
2165+ # GIVEN: A mocked :func:`shutil.which` when the command is not found.
2166+ with patch('openlp.core.common.path.shutil.which', return_value=None) as mocked_shutil_which:
2167+
2168+ # WHEN: Calling :func:`openlp.core.common.path.which` with a command that does not exist.
2169+ result = which('no_command')
2170+
2171+ # THEN: :func:`shutil.which` should have been called with the command, and :func:`which` should return None.
2172+ mocked_shutil_which.assert_called_once_with('no_command')
2173+ self.assertIsNone(result)
2174+
2175+ def test_which_command(self):
2176+ """
2177+ Test :func:`openlp.core.common.path.which` when a command has been found.
2178+ """
2179+ # GIVEN: A mocked :func:`shutil.which` when the command is found.
2180+ with patch('openlp.core.common.path.shutil.which',
2181+ return_value=os.path.join('path', 'to', 'command')) as mocked_shutil_which:
2182+
2183+ # WHEN: Calling :func:`openlp.core.common.path.which` with a command that exists.
2184+ result = which('command')
2185+
2186+ # THEN: :func:`shutil.which` should have been called with the command, and :func:`which` should return a
2187+ # Path object equivalent of the command path.
2188+ mocked_shutil_which.assert_called_once_with('command')
2189+ self.assertEqual(result, Path('path', 'to', 'command'))
2190
2191
2192 class TestPath(TestCase):
2193
2194=== modified file 'tests/functional/openlp_core_lib/test_lib.py'
2195--- tests/functional/openlp_core_lib/test_lib.py 2017-08-25 20:03:25 +0000
2196+++ tests/functional/openlp_core_lib/test_lib.py 2017-09-21 08:29:37 +0000
2197@@ -32,7 +32,7 @@
2198 from openlp.core.common.path import Path
2199 from openlp.core.lib import FormattingTags, build_icon, check_item_selected, clean_tags, compare_chord_lyric, \
2200 create_separated_list, create_thumb, expand_chords, expand_chords_for_printing, expand_tags, find_formatting_tags, \
2201- get_text_file_string, image_to_byte, replace_params, resize_image, str_to_bool, validate_thumb
2202+ get_text_file_string, image_to_byte, resize_image, str_to_bool, validate_thumb
2203
2204 TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', 'resources'))
2205
2206@@ -595,93 +595,46 @@
2207 Test the validate_thumb() function when the thumbnail does not exist
2208 """
2209 # GIVEN: A mocked out os module, with path.exists returning False, and fake paths to a file and a thumb
2210- with patch('openlp.core.lib.os') as mocked_os:
2211- file_path = 'path/to/file'
2212- thumb_path = 'path/to/thumb'
2213- mocked_os.path.exists.return_value = False
2214+ with patch.object(Path, 'exists', return_value=False) as mocked_path_exists:
2215+ file_path = Path('path', 'to', 'file')
2216+ thumb_path = Path('path', 'to', 'thumb')
2217
2218 # WHEN: we run the validate_thumb() function
2219 result = validate_thumb(file_path, thumb_path)
2220
2221 # THEN: we should have called a few functions, and the result should be False
2222- mocked_os.path.exists.assert_called_with(thumb_path)
2223- assert result is False, 'The result should be False'
2224+ thumb_path.exists.assert_called_once_with()
2225+ self.assertFalse(result, 'The result should be False')
2226
2227 def test_validate_thumb_file_exists_and_newer(self):
2228 """
2229 Test the validate_thumb() function when the thumbnail exists and has a newer timestamp than the file
2230 """
2231- # GIVEN: A mocked out os module, functions rigged to work for us, and fake paths to a file and a thumb
2232- with patch('openlp.core.lib.os') as mocked_os:
2233- file_path = 'path/to/file'
2234- thumb_path = 'path/to/thumb'
2235- file_mocked_stat = MagicMock()
2236- file_mocked_stat.st_mtime = datetime.now()
2237- thumb_mocked_stat = MagicMock()
2238- thumb_mocked_stat.st_mtime = datetime.now() + timedelta(seconds=10)
2239- mocked_os.path.exists.return_value = True
2240- mocked_os.stat.side_effect = [file_mocked_stat, thumb_mocked_stat]
2241+ with patch.object(Path, 'exists'), patch.object(Path, 'stat'):
2242+ # GIVEN: Mocked file_path and thumb_path which return different values fo the modified times
2243+ file_path = MagicMock(**{'stat.return_value': MagicMock(st_mtime=10)})
2244+ thumb_path = MagicMock(**{'exists.return_value': True, 'stat.return_value': MagicMock(st_mtime=11)})
2245
2246 # WHEN: we run the validate_thumb() function
2247+ result = validate_thumb(file_path, thumb_path)
2248
2249- # THEN: we should have called a few functions, and the result should be True
2250- # mocked_os.path.exists.assert_called_with(thumb_path)
2251+ # THEN: `validate_thumb` should return True
2252+ self.assertTrue(result)
2253
2254 def test_validate_thumb_file_exists_and_older(self):
2255 """
2256 Test the validate_thumb() function when the thumbnail exists but is older than the file
2257 """
2258- # GIVEN: A mocked out os module, functions rigged to work for us, and fake paths to a file and a thumb
2259- with patch('openlp.core.lib.os') as mocked_os:
2260- file_path = 'path/to/file'
2261- thumb_path = 'path/to/thumb'
2262- file_mocked_stat = MagicMock()
2263- file_mocked_stat.st_mtime = datetime.now()
2264- thumb_mocked_stat = MagicMock()
2265- thumb_mocked_stat.st_mtime = datetime.now() - timedelta(seconds=10)
2266- mocked_os.path.exists.return_value = True
2267- mocked_os.stat.side_effect = lambda fname: file_mocked_stat if fname == file_path else thumb_mocked_stat
2268-
2269- # WHEN: we run the validate_thumb() function
2270- result = validate_thumb(file_path, thumb_path)
2271-
2272- # THEN: we should have called a few functions, and the result should be False
2273- mocked_os.path.exists.assert_called_with(thumb_path)
2274- mocked_os.stat.assert_any_call(file_path)
2275- mocked_os.stat.assert_any_call(thumb_path)
2276- assert result is False, 'The result should be False'
2277-
2278- def test_replace_params_no_params(self):
2279- """
2280- Test replace_params when called with and empty tuple instead of parameters to replace
2281- """
2282- # GIVEN: Some test data
2283- test_args = (1, 2)
2284- test_kwargs = {'arg3': 3, 'arg4': 4}
2285- test_params = tuple()
2286-
2287- # WHEN: Calling replace_params
2288- result_args, result_kwargs = replace_params(test_args, test_kwargs, test_params)
2289-
2290- # THEN: The positional and keyword args should not have changed
2291- self.assertEqual(test_args, result_args)
2292- self.assertEqual(test_kwargs, result_kwargs)
2293-
2294- def test_replace_params_params(self):
2295- """
2296- Test replace_params when given a positional and a keyword argument to change
2297- """
2298- # GIVEN: Some test data
2299- test_args = (1, 2)
2300- test_kwargs = {'arg3': 3, 'arg4': 4}
2301- test_params = ((1, 'arg2', str), (2, 'arg3', str))
2302-
2303- # WHEN: Calling replace_params
2304- result_args, result_kwargs = replace_params(test_args, test_kwargs, test_params)
2305-
2306- # THEN: The positional and keyword args should have have changed
2307- self.assertEqual(result_args, (1, '2'))
2308- self.assertEqual(result_kwargs, {'arg3': '3', 'arg4': 4})
2309+ # GIVEN: Mocked file_path and thumb_path which return different values fo the modified times
2310+ file_path = MagicMock(**{'stat.return_value': MagicMock(st_mtime=10)})
2311+ thumb_path = MagicMock(**{'exists.return_value': True, 'stat.return_value': MagicMock(st_mtime=9)})
2312+
2313+ # WHEN: we run the validate_thumb() function
2314+ result = validate_thumb(file_path, thumb_path)
2315+
2316+ # THEN: `validate_thumb` should return False
2317+ thumb_path.stat.assert_called_once_with()
2318+ self.assertFalse(result, 'The result should be False')
2319
2320 def test_resize_thumb(self):
2321 """
2322
2323=== modified file 'tests/functional/openlp_core_ui/test_exceptionform.py'
2324--- tests/functional/openlp_core_ui/test_exceptionform.py 2017-08-26 15:06:11 +0000
2325+++ tests/functional/openlp_core_ui/test_exceptionform.py 2017-09-21 08:29:37 +0000
2326@@ -103,7 +103,7 @@
2327 os.remove(self.tempfile)
2328
2329 @patch("openlp.core.ui.exceptionform.Ui_ExceptionDialog")
2330- @patch("openlp.core.ui.exceptionform.QtWidgets.QFileDialog")
2331+ @patch("openlp.core.ui.exceptionform.FileDialog")
2332 @patch("openlp.core.ui.exceptionform.QtCore.QUrl")
2333 @patch("openlp.core.ui.exceptionform.QtCore.QUrlQuery.addQueryItem")
2334 @patch("openlp.core.ui.exceptionform.Qt")
2335
2336=== modified file 'tests/functional/openlp_plugins/presentations/test_impresscontroller.py'
2337--- tests/functional/openlp_plugins/presentations/test_impresscontroller.py 2017-06-08 21:36:17 +0000
2338+++ tests/functional/openlp_plugins/presentations/test_impresscontroller.py 2017-09-21 08:29:37 +0000
2339@@ -24,13 +24,12 @@
2340 """
2341 from unittest import TestCase
2342 from unittest.mock import MagicMock
2343-import os
2344 import shutil
2345 from tempfile import mkdtemp
2346
2347 from openlp.core.common import Settings
2348-from openlp.plugins.presentations.lib.impresscontroller import \
2349- ImpressController, ImpressDocument, TextType
2350+from openlp.core.common.path import Path
2351+from openlp.plugins.presentations.lib.impresscontroller import ImpressController, ImpressDocument, TextType
2352 from openlp.plugins.presentations.presentationplugin import __default_settings__
2353
2354 from tests.utils.constants import TEST_RESOURCES_PATH
2355@@ -82,7 +81,7 @@
2356 mocked_plugin = MagicMock()
2357 mocked_plugin.settings_section = 'presentations'
2358 Settings().extend_default_settings(__default_settings__)
2359- self.file_name = os.path.join(TEST_RESOURCES_PATH, 'presentations', 'test.pptx')
2360+ self.file_name = Path(TEST_RESOURCES_PATH, 'presentations', 'test.pptx')
2361 self.ppc = ImpressController(mocked_plugin)
2362 self.doc = ImpressDocument(self.ppc, self.file_name)
2363
2364
2365=== modified file 'tests/functional/openlp_plugins/presentations/test_mediaitem.py'
2366--- tests/functional/openlp_plugins/presentations/test_mediaitem.py 2017-04-24 05:17:55 +0000
2367+++ tests/functional/openlp_plugins/presentations/test_mediaitem.py 2017-09-21 08:29:37 +0000
2368@@ -26,6 +26,7 @@
2369 from unittest.mock import patch, MagicMock, call
2370
2371 from openlp.core.common import Registry
2372+from openlp.core.common.path import Path
2373 from openlp.plugins.presentations.lib.mediaitem import PresentationMediaItem
2374
2375 from tests.helpers.testmixin import TestMixin
2376@@ -92,17 +93,18 @@
2377 """
2378 # GIVEN: A mocked controller, and mocked os.path.getmtime
2379 mocked_controller = MagicMock()
2380- mocked_doc = MagicMock()
2381+ mocked_doc = MagicMock(**{'get_thumbnail_path.return_value': Path()})
2382 mocked_controller.add_document.return_value = mocked_doc
2383 mocked_controller.supports = ['tmp']
2384 self.media_item.controllers = {
2385 'Mocked': mocked_controller
2386 }
2387- presentation_file = 'file.tmp'
2388- with patch('openlp.plugins.presentations.lib.mediaitem.os.path.getmtime') as mocked_getmtime, \
2389- patch('openlp.plugins.presentations.lib.mediaitem.os.path.exists') as mocked_exists:
2390- mocked_getmtime.side_effect = [100, 200]
2391- mocked_exists.return_value = True
2392+
2393+ thmub_path = MagicMock(st_mtime=100)
2394+ file_path = MagicMock(st_mtime=400)
2395+ with patch.object(Path, 'stat', side_effect=[thmub_path, file_path]), \
2396+ patch.object(Path, 'exists', return_value=True):
2397+ presentation_file = Path('file.tmp')
2398
2399 # WHEN: calling clean_up_thumbnails
2400 self.media_item.clean_up_thumbnails(presentation_file, True)
2401@@ -123,9 +125,8 @@
2402 self.media_item.controllers = {
2403 'Mocked': mocked_controller
2404 }
2405- presentation_file = 'file.tmp'
2406- with patch('openlp.plugins.presentations.lib.mediaitem.os.path.exists') as mocked_exists:
2407- mocked_exists.return_value = False
2408+ presentation_file = Path('file.tmp')
2409+ with patch.object(Path, 'exists', return_value=False):
2410
2411 # WHEN: calling clean_up_thumbnails
2412 self.media_item.clean_up_thumbnails(presentation_file, True)
2413
2414=== modified file 'tests/functional/openlp_plugins/presentations/test_pdfcontroller.py'
2415--- tests/functional/openlp_plugins/presentations/test_pdfcontroller.py 2017-04-24 05:17:55 +0000
2416+++ tests/functional/openlp_plugins/presentations/test_pdfcontroller.py 2017-09-21 08:29:37 +0000
2417@@ -32,6 +32,7 @@
2418
2419 from openlp.plugins.presentations.lib.pdfcontroller import PdfController, PdfDocument
2420 from openlp.core.common import Settings
2421+from openlp.core.common.path import Path
2422 from openlp.core.lib import ScreenList
2423
2424 from tests.utils.constants import TEST_RESOURCES_PATH
2425@@ -66,8 +67,8 @@
2426 self.desktop.screenGeometry.return_value = SCREEN['size']
2427 self.screens = ScreenList.create(self.desktop)
2428 Settings().extend_default_settings(__default_settings__)
2429- self.temp_folder = mkdtemp()
2430- self.thumbnail_folder = mkdtemp()
2431+ self.temp_folder = Path(mkdtemp())
2432+ self.thumbnail_folder = Path(mkdtemp())
2433 self.mock_plugin = MagicMock()
2434 self.mock_plugin.settings_section = self.temp_folder
2435
2436@@ -77,8 +78,8 @@
2437 """
2438 del self.screens
2439 self.destroy_settings()
2440- shutil.rmtree(self.thumbnail_folder)
2441- shutil.rmtree(self.temp_folder)
2442+ shutil.rmtree(str(self.thumbnail_folder))
2443+ shutil.rmtree(str(self.temp_folder))
2444
2445 def test_constructor(self):
2446 """
2447@@ -98,7 +99,7 @@
2448 Test loading of a Pdf using the PdfController
2449 """
2450 # GIVEN: A Pdf-file
2451- test_file = os.path.join(TEST_RESOURCES_PATH, 'presentations', 'pdf_test1.pdf')
2452+ test_file = Path(TEST_RESOURCES_PATH, 'presentations', 'pdf_test1.pdf')
2453
2454 # WHEN: The Pdf is loaded
2455 controller = PdfController(plugin=self.mock_plugin)
2456@@ -118,7 +119,7 @@
2457 Test loading of a Pdf and check size of generate pictures
2458 """
2459 # GIVEN: A Pdf-file
2460- test_file = os.path.join(TEST_RESOURCES_PATH, 'presentations', 'pdf_test1.pdf')
2461+ test_file = Path(TEST_RESOURCES_PATH, 'presentations', 'pdf_test1.pdf')
2462
2463 # WHEN: The Pdf is loaded
2464 controller = PdfController(plugin=self.mock_plugin)
2465@@ -131,7 +132,7 @@
2466
2467 # THEN: The load should succeed and pictures should be created and have been scales to fit the screen
2468 self.assertTrue(loaded, 'The loading of the PDF should succeed.')
2469- image = QtGui.QImage(os.path.join(self.temp_folder, 'pdf_test1.pdf', 'mainslide001.png'))
2470+ image = QtGui.QImage(os.path.join(str(self.temp_folder), 'pdf_test1.pdf', 'mainslide001.png'))
2471 # Based on the converter used the resolution will differ a bit
2472 if controller.gsbin:
2473 self.assertEqual(760, image.height(), 'The height should be 760')
2474
2475=== modified file 'tests/functional/openlp_plugins/presentations/test_pptviewcontroller.py'
2476--- tests/functional/openlp_plugins/presentations/test_pptviewcontroller.py 2017-05-30 18:42:35 +0000
2477+++ tests/functional/openlp_plugins/presentations/test_pptviewcontroller.py 2017-09-21 08:29:37 +0000
2478@@ -22,7 +22,6 @@
2479 """
2480 This module contains tests for the pptviewcontroller module of the Presentations plugin.
2481 """
2482-import os
2483 import shutil
2484 from tempfile import mkdtemp
2485 from unittest import TestCase
2486@@ -30,6 +29,7 @@
2487
2488 from openlp.plugins.presentations.lib.pptviewcontroller import PptviewDocument, PptviewController
2489 from openlp.core.common import is_win
2490+from openlp.core.common.path import Path
2491
2492 from tests.helpers.testmixin import TestMixin
2493 from tests.utils.constants import TEST_RESOURCES_PATH
2494@@ -184,7 +184,7 @@
2495 """
2496 # GIVEN: mocked PresentationController.save_titles_and_notes and a pptx file
2497 doc = PptviewDocument(self.mock_controller, self.mock_presentation)
2498- doc.file_path = os.path.join(TEST_RESOURCES_PATH, 'presentations', 'test.pptx')
2499+ doc.file_path = Path(TEST_RESOURCES_PATH, 'presentations', 'test.pptx')
2500 doc.save_titles_and_notes = MagicMock()
2501
2502 # WHEN reading the titles and notes
2503@@ -201,13 +201,13 @@
2504 """
2505 # GIVEN: mocked PresentationController.save_titles_and_notes and an nonexistent file
2506 with patch('builtins.open') as mocked_open, \
2507- patch('openlp.plugins.presentations.lib.pptviewcontroller.os.path.exists') as mocked_exists, \
2508+ patch.object(Path, 'exists') as mocked_path_exists, \
2509 patch('openlp.plugins.presentations.lib.presentationcontroller.check_directory_exists') as \
2510 mocked_dir_exists:
2511- mocked_exists.return_value = False
2512+ mocked_path_exists.return_value = False
2513 mocked_dir_exists.return_value = False
2514 doc = PptviewDocument(self.mock_controller, self.mock_presentation)
2515- doc.file_path = 'Idontexist.pptx'
2516+ doc.file_path = Path('Idontexist.pptx')
2517 doc.save_titles_and_notes = MagicMock()
2518
2519 # WHEN: Reading the titles and notes
2520@@ -215,7 +215,7 @@
2521
2522 # THEN: File existens should have been checked, and not have been opened.
2523 doc.save_titles_and_notes.assert_called_once_with(None, None)
2524- mocked_exists.assert_any_call('Idontexist.pptx')
2525+ mocked_path_exists.assert_called_with()
2526 self.assertEqual(mocked_open.call_count, 0, 'There should be no calls to open a file.')
2527
2528 def test_create_titles_and_notes_invalid_file(self):
2529@@ -228,7 +228,7 @@
2530 mocked_is_zf.return_value = False
2531 mocked_open.filesize = 10
2532 doc = PptviewDocument(self.mock_controller, self.mock_presentation)
2533- doc.file_path = os.path.join(TEST_RESOURCES_PATH, 'presentations', 'test.ppt')
2534+ doc.file_path = Path(TEST_RESOURCES_PATH, 'presentations', 'test.ppt')
2535 doc.save_titles_and_notes = MagicMock()
2536
2537 # WHEN: reading the titles and notes
2538
2539=== modified file 'tests/functional/openlp_plugins/presentations/test_presentationcontroller.py'
2540--- tests/functional/openlp_plugins/presentations/test_presentationcontroller.py 2017-08-25 20:03:25 +0000
2541+++ tests/functional/openlp_plugins/presentations/test_presentationcontroller.py 2017-09-21 08:29:37 +0000
2542@@ -23,9 +23,8 @@
2543 Functional tests to test the PresentationController and PresentationDocument
2544 classes and related methods.
2545 """
2546-import os
2547 from unittest import TestCase
2548-from unittest.mock import MagicMock, mock_open, patch
2549+from unittest.mock import MagicMock, call, patch
2550
2551 from openlp.core.common.path import Path
2552 from openlp.plugins.presentations.lib.presentationcontroller import PresentationController, PresentationDocument
2553@@ -67,23 +66,18 @@
2554 Test PresentationDocument.save_titles_and_notes method with two valid lists
2555 """
2556 # GIVEN: two lists of length==2 and a mocked open and get_thumbnail_folder
2557- mocked_open = mock_open()
2558- with patch('builtins.open', mocked_open), patch(FOLDER_TO_PATCH) as mocked_get_thumbnail_folder:
2559+ with patch('openlp.plugins.presentations.lib.presentationcontroller.Path.write_text') as mocked_write_text, \
2560+ patch(FOLDER_TO_PATCH) as mocked_get_thumbnail_folder:
2561 titles = ['uno', 'dos']
2562 notes = ['one', 'two']
2563
2564 # WHEN: calling save_titles_and_notes
2565- mocked_get_thumbnail_folder.return_value = 'test'
2566+ mocked_get_thumbnail_folder.return_value = Path('test')
2567 self.document.save_titles_and_notes(titles, notes)
2568
2569 # THEN: the last call to open should have been for slideNotes2.txt
2570- mocked_open.assert_any_call(os.path.join('test', 'titles.txt'), mode='wt', encoding='utf-8')
2571- mocked_open.assert_any_call(os.path.join('test', 'slideNotes1.txt'), mode='wt', encoding='utf-8')
2572- mocked_open.assert_any_call(os.path.join('test', 'slideNotes2.txt'), mode='wt', encoding='utf-8')
2573- self.assertEqual(mocked_open.call_count, 3, 'There should be exactly three files opened')
2574- mocked_open().writelines.assert_called_once_with(['uno', 'dos'])
2575- mocked_open().write.assert_any_call('one')
2576- mocked_open().write.assert_any_call('two')
2577+ self.assertEqual(mocked_write_text.call_count, 3, 'There should be exactly three files written')
2578+ mocked_write_text.assert_has_calls([call('uno\ndos'), call('one'), call('two')])
2579
2580 def test_save_titles_and_notes_with_None(self):
2581 """
2582@@ -107,10 +101,11 @@
2583 """
2584 # GIVEN: A mocked open, get_thumbnail_folder and exists
2585
2586- with patch('builtins.open', mock_open(read_data='uno\ndos\n')) as mocked_open, \
2587+ with patch('openlp.plugins.presentations.lib.presentationcontroller.Path.read_text',
2588+ return_value='uno\ndos\n') as mocked_read_text, \
2589 patch(FOLDER_TO_PATCH) as mocked_get_thumbnail_folder, \
2590- patch('openlp.plugins.presentations.lib.presentationcontroller.os.path.exists') as mocked_exists:
2591- mocked_get_thumbnail_folder.return_value = 'test'
2592+ patch('openlp.plugins.presentations.lib.presentationcontroller.Path.exists') as mocked_exists:
2593+ mocked_get_thumbnail_folder.return_value = Path('test')
2594 mocked_exists.return_value = True
2595
2596 # WHEN: calling get_titles_and_notes
2597@@ -121,45 +116,36 @@
2598 self.assertEqual(len(result_titles), 2, 'There should be two items in the titles')
2599 self.assertIs(type(result_notes), list, 'result_notes should be of type list')
2600 self.assertEqual(len(result_notes), 2, 'There should be two items in the notes')
2601- self.assertEqual(mocked_open.call_count, 3, 'Three files should be opened')
2602- mocked_open.assert_any_call(os.path.join('test', 'titles.txt'), encoding='utf-8')
2603- mocked_open.assert_any_call(os.path.join('test', 'slideNotes1.txt'), encoding='utf-8')
2604- mocked_open.assert_any_call(os.path.join('test', 'slideNotes2.txt'), encoding='utf-8')
2605- self.assertEqual(mocked_exists.call_count, 3, 'Three files should have been checked')
2606+ self.assertEqual(mocked_read_text.call_count, 3, 'Three files should be read')
2607
2608 def test_get_titles_and_notes_with_file_not_found(self):
2609 """
2610 Test PresentationDocument.get_titles_and_notes method with file not found
2611 """
2612 # GIVEN: A mocked open, get_thumbnail_folder and exists
2613- with patch('builtins.open') as mocked_open, \
2614- patch(FOLDER_TO_PATCH) as mocked_get_thumbnail_folder, \
2615- patch('openlp.plugins.presentations.lib.presentationcontroller.os.path.exists') as mocked_exists:
2616- mocked_get_thumbnail_folder.return_value = 'test'
2617- mocked_exists.return_value = False
2618+ with patch('openlp.plugins.presentations.lib.presentationcontroller.Path.read_text') as mocked_read_text, \
2619+ patch(FOLDER_TO_PATCH) as mocked_get_thumbnail_folder:
2620+ mocked_read_text.side_effect = FileNotFoundError()
2621+ mocked_get_thumbnail_folder.return_value = Path('test')
2622
2623 # WHEN: calling get_titles_and_notes
2624 result_titles, result_notes = self.document.get_titles_and_notes()
2625
2626 # THEN: it should return two empty lists
2627- self.assertIs(type(result_titles), list, 'result_titles should be of type list')
2628+ self.assertIsInstance(result_titles, list, 'result_titles should be of type list')
2629 self.assertEqual(len(result_titles), 0, 'there be no titles')
2630- self.assertIs(type(result_notes), list, 'result_notes should be a list')
2631+ self.assertIsInstance(result_notes, list, 'result_notes should be a list')
2632 self.assertEqual(len(result_notes), 0, 'but the list should be empty')
2633- self.assertEqual(mocked_open.call_count, 0, 'No calls to open files')
2634- self.assertEqual(mocked_exists.call_count, 1, 'There should be one call to file exists')
2635
2636 def test_get_titles_and_notes_with_file_error(self):
2637 """
2638 Test PresentationDocument.get_titles_and_notes method with file errors
2639 """
2640 # GIVEN: A mocked open, get_thumbnail_folder and exists
2641- with patch('builtins.open') as mocked_open, \
2642- patch(FOLDER_TO_PATCH) as mocked_get_thumbnail_folder, \
2643- patch('openlp.plugins.presentations.lib.presentationcontroller.os.path.exists') as mocked_exists:
2644- mocked_get_thumbnail_folder.return_value = 'test'
2645- mocked_exists.return_value = True
2646- mocked_open.side_effect = IOError()
2647+ with patch('openlp.plugins.presentations.lib.presentationcontroller.Path.read_text') as mocked_read_text, \
2648+ patch(FOLDER_TO_PATCH) as mocked_get_thumbnail_folder:
2649+ mocked_read_text.side_effect = IOError()
2650+ mocked_get_thumbnail_folder.return_value = Path('test')
2651
2652 # WHEN: calling get_titles_and_notes
2653 result_titles, result_notes = self.document.get_titles_and_notes()
2654@@ -180,18 +166,16 @@
2655 patch('openlp.plugins.presentations.lib.presentationcontroller.check_directory_exists')
2656 self.get_thumbnail_folder_patcher = \
2657 patch('openlp.plugins.presentations.lib.presentationcontroller.PresentationDocument.get_thumbnail_folder')
2658- self.os_patcher = patch('openlp.plugins.presentations.lib.presentationcontroller.os')
2659 self._setup_patcher = \
2660 patch('openlp.plugins.presentations.lib.presentationcontroller.PresentationDocument._setup')
2661
2662 self.mock_check_directory_exists = self.check_directory_exists_patcher.start()
2663 self.mock_get_thumbnail_folder = self.get_thumbnail_folder_patcher.start()
2664- self.mock_os = self.os_patcher.start()
2665 self.mock_setup = self._setup_patcher.start()
2666
2667 self.mock_controller = MagicMock()
2668
2669- self.mock_get_thumbnail_folder.return_value = 'returned/path/'
2670+ self.mock_get_thumbnail_folder.return_value = Path('returned/path/')
2671
2672 def tearDown(self):
2673 """
2674@@ -199,7 +183,6 @@
2675 """
2676 self.check_directory_exists_patcher.stop()
2677 self.get_thumbnail_folder_patcher.stop()
2678- self.os_patcher.stop()
2679 self._setup_patcher.stop()
2680
2681 def test_initialise_presentation_document(self):
2682@@ -227,7 +210,7 @@
2683 PresentationDocument(self.mock_controller, 'Name')
2684
2685 # THEN: check_directory_exists should have been called with 'returned/path/'
2686- self.mock_check_directory_exists.assert_called_once_with(Path('returned', 'path'))
2687+ self.mock_check_directory_exists.assert_called_once_with(Path('returned', 'path/'))
2688
2689 self._setup_patcher.start()
2690
2691@@ -244,20 +227,3 @@
2692
2693 # THEN: load_presentation should return false
2694 self.assertFalse(result, "PresentationDocument.load_presentation should return false.")
2695-
2696- def test_get_file_name(self):
2697- """
2698- Test the PresentationDocument.get_file_name method.
2699- """
2700-
2701- # GIVEN: A mocked os.path.split which returns a list, an instance of PresentationDocument and
2702- # arbitary file_path.
2703- self.mock_os.path.split.return_value = ['directory', 'file.ext']
2704- instance = PresentationDocument(self.mock_controller, 'Name')
2705- instance.file_path = 'filepath'
2706-
2707- # WHEN: Calling get_file_name
2708- result = instance.get_file_name()
2709-
2710- # THEN: get_file_name should return 'file.ext'
2711- self.assertEqual(result, 'file.ext')