Status: | Merged |
---|---|
Merged at revision: | 2769 |
Proposed branch: | lp:~phill-ridout/openlp/pathlib6 |
Merge into: | lp:openlp |
Diff against target: |
1335 lines (+392/-213) 20 files modified
openlp/core/common/httputils.py (+0/-1) openlp/core/lib/db.py (+68/-15) openlp/core/ui/advancedtab.py (+14/-11) openlp/core/ui/exceptionform.py (+2/-12) openlp/core/ui/lib/filedialog.py (+8/-8) openlp/core/ui/mainwindow.py (+4/-10) openlp/core/ui/servicemanager.py (+5/-3) openlp/plugins/alerts/forms/alertform.py (+1/-1) openlp/plugins/alerts/lib/alertstab.py (+0/-3) openlp/plugins/custom/lib/customtab.py (+0/-3) openlp/plugins/images/imageplugin.py (+2/-2) openlp/plugins/images/lib/db.py (+3/-4) openlp/plugins/images/lib/imagetab.py (+0/-3) openlp/plugins/images/lib/mediaitem.py (+67/-65) openlp/plugins/images/lib/upgrade.py (+70/-0) openlp/plugins/songusage/forms/songusagedetaildialog.py (+0/-1) openlp/plugins/songusage/forms/songusagedetailform.py (+17/-22) tests/functional/openlp_core_ui/test_exceptionform.py (+28/-29) tests/functional/openlp_plugins/images/test_lib.py (+20/-20) tests/functional/openlp_plugins/images/test_upgrade.py (+83/-0) |
To merge this branch: | bzr merge lp:~phill-ridout/openlp/pathlib6 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Tomas Groth | Approve | ||
Tim Bentley | Approve | ||
Phill | Pending | ||
Review via email: mp+331276@code.launchpad.net |
This proposal supersedes a proposal from 2017-09-24.
Commit message
Description of the change
This is ready to go now!
Upgrade the image plugin to use pathlib.
Other minor changes and fixes.
Add this to your merge proposal:
-------
lp:~phill-ridout/openlp/pathlib6 (revision 2774)
[SUCCESS] https:/
[SUCCESS] https:/
[SUCCESS] https:/
[SUCCESS] https:/
[SUCCESS] https:/
[SUCCESS] https:/
[FAILURE] https:/
Stopping after failure
Tim Bentley (trb143) wrote : Posted in a previous version of this proposal | # |
Phill (phill-ridout) wrote : Posted in a previous version of this proposal | # |
those __init__ methods just call the super method. They don't do anything I
fail to see any reason for keeping them!
On Sun, 24 Sep 2017, 22:01 Tim Bentley <email address hidden> wrote:
> Review: Needs Information
>
> Why have you removed a couple of __init__ methods?
> --
> https:/
> You are the owner of lp:~phill-ridout/openlp/pathlib6.
>
Phill (phill-ridout) wrote : Posted in a previous version of this proposal | # |
Just noticed one minor mistake.
Tim Bentley (trb143) : | # |
Tomas Groth (tomasgroth) wrote : | # |
Just a minor thing, see inline comment.
Tomas Groth (tomasgroth) : | # |
Preview Diff
1 | === modified file 'openlp/core/common/httputils.py' |
2 | --- openlp/core/common/httputils.py 2017-09-06 20:18:08 +0000 |
3 | +++ openlp/core/common/httputils.py 2017-09-25 17:10:35 +0000 |
4 | @@ -24,7 +24,6 @@ |
5 | """ |
6 | import hashlib |
7 | import logging |
8 | -import os |
9 | import platform |
10 | import socket |
11 | import sys |
12 | |
13 | === modified file 'openlp/core/lib/db.py' |
14 | --- openlp/core/lib/db.py 2017-08-12 17:45:56 +0000 |
15 | +++ openlp/core/lib/db.py 2017-09-25 17:10:35 +0000 |
16 | @@ -23,12 +23,13 @@ |
17 | """ |
18 | The :mod:`db` module provides the core database functionality for OpenLP |
19 | """ |
20 | +import json |
21 | import logging |
22 | import os |
23 | from copy import copy |
24 | from urllib.parse import quote_plus as urlquote |
25 | |
26 | -from sqlalchemy import Table, MetaData, Column, types, create_engine |
27 | +from sqlalchemy import Table, MetaData, Column, types, create_engine, UnicodeText |
28 | from sqlalchemy.engine.url import make_url |
29 | from sqlalchemy.exc import SQLAlchemyError, InvalidRequestError, DBAPIError, OperationalError, ProgrammingError |
30 | from sqlalchemy.orm import scoped_session, sessionmaker, mapper |
31 | @@ -37,7 +38,8 @@ |
32 | from alembic.migration import MigrationContext |
33 | from alembic.operations import Operations |
34 | |
35 | -from openlp.core.common import AppLocation, Settings, translate, delete_file |
36 | +from openlp.core.common import AppLocation, Settings, delete_file, translate |
37 | +from openlp.core.common.json import OpenLPJsonDecoder, OpenLPJsonEncoder |
38 | from openlp.core.lib.ui import critical_error_message_box |
39 | |
40 | log = logging.getLogger(__name__) |
41 | @@ -133,9 +135,10 @@ |
42 | if db_file_name is None: |
43 | return 'sqlite:///{path}/{plugin}.sqlite'.format(path=AppLocation.get_section_data_path(plugin_name), |
44 | plugin=plugin_name) |
45 | + elif os.path.isabs(db_file_name): |
46 | + return 'sqlite:///{db_file_name}'.format(db_file_name=db_file_name) |
47 | else: |
48 | - return 'sqlite:///{path}/{name}'.format(path=AppLocation.get_section_data_path(plugin_name), |
49 | - name=db_file_name) |
50 | + return 'sqlite:///{path}/{name}'.format(path=AppLocation.get_section_data_path(plugin_name), name=db_file_name) |
51 | |
52 | |
53 | def handle_db_error(plugin_name, db_file_name): |
54 | @@ -200,6 +203,55 @@ |
55 | return instance |
56 | |
57 | |
58 | +class PathType(types.TypeDecorator): |
59 | + """ |
60 | + Create a PathType for storing Path objects with SQLAlchemy. Behind the scenes we convert the Path object to a JSON |
61 | + representation and store it as a Unicode type |
62 | + """ |
63 | + impl = types.UnicodeText |
64 | + |
65 | + def coerce_compared_value(self, op, value): |
66 | + """ |
67 | + Some times it make sense to compare a PathType with a string. In the case a string is used coerce the the |
68 | + PathType to a UnicodeText type. |
69 | + |
70 | + :param op: The operation being carried out. Not used, as we only care about the type that is being used with the |
71 | + operation. |
72 | + :param openlp.core.common.path.Path | str value: The value being used for the comparison. Most likely a Path |
73 | + Object or str. |
74 | + :return: The coerced value stored in the db |
75 | + :rtype: PathType or UnicodeText |
76 | + """ |
77 | + if isinstance(value, str): |
78 | + return UnicodeText() |
79 | + else: |
80 | + return self |
81 | + |
82 | + def process_bind_param(self, value, dialect): |
83 | + """ |
84 | + Convert the Path object to a JSON representation |
85 | + |
86 | + :param openlp.core.common.path.Path value: The value to convert |
87 | + :param dialect: Not used |
88 | + :return: The Path object as a JSON string |
89 | + :rtype: str |
90 | + """ |
91 | + data_path = AppLocation.get_data_path() |
92 | + return json.dumps(value, cls=OpenLPJsonEncoder, base_path=data_path) |
93 | + |
94 | + def process_result_value(self, value, dialect): |
95 | + """ |
96 | + Convert the JSON representation back |
97 | + |
98 | + :param types.UnicodeText value: The value to convert |
99 | + :param dialect: Not used |
100 | + :return: The JSON object converted Python object (in this case it should be a Path object) |
101 | + :rtype: openlp.core.common.path.Path |
102 | + """ |
103 | + data_path = AppLocation.get_data_path() |
104 | + return json.loads(value, cls=OpenLPJsonDecoder, base_path=data_path) |
105 | + |
106 | + |
107 | def upgrade_db(url, upgrade): |
108 | """ |
109 | Upgrade a database. |
110 | @@ -208,7 +260,7 @@ |
111 | :param upgrade: The python module that contains the upgrade instructions. |
112 | """ |
113 | if not database_exists(url): |
114 | - log.warn("Database {db} doesn't exist - skipping upgrade checks".format(db=url)) |
115 | + log.warning("Database {db} doesn't exist - skipping upgrade checks".format(db=url)) |
116 | return (0, 0) |
117 | |
118 | log.debug('Checking upgrades for DB {db}'.format(db=url)) |
119 | @@ -273,10 +325,11 @@ |
120 | :param plugin_name: The name of the plugin to remove the database for |
121 | :param db_file_name: The database file name. Defaults to None resulting in the plugin_name being used. |
122 | """ |
123 | + db_file_path = AppLocation.get_section_data_path(plugin_name) |
124 | if db_file_name: |
125 | - db_file_path = AppLocation.get_section_data_path(plugin_name) / db_file_name |
126 | + db_file_path = db_file_path / db_file_name |
127 | else: |
128 | - db_file_path = AppLocation.get_section_data_path(plugin_name) / plugin_name |
129 | + db_file_path = db_file_path / plugin_name |
130 | return delete_file(db_file_path) |
131 | |
132 | |
133 | @@ -284,30 +337,30 @@ |
134 | """ |
135 | Provide generic object persistence management |
136 | """ |
137 | - def __init__(self, plugin_name, init_schema, db_file_name=None, upgrade_mod=None, session=None): |
138 | + def __init__(self, plugin_name, init_schema, db_file_path=None, upgrade_mod=None, session=None): |
139 | """ |
140 | Runs the initialisation process that includes creating the connection to the database and the tables if they do |
141 | not exist. |
142 | |
143 | :param plugin_name: The name to setup paths and settings section names |
144 | :param init_schema: The init_schema function for this database |
145 | - :param db_file_name: The upgrade_schema function for this database |
146 | - :param upgrade_mod: The file name to use for this database. Defaults to None resulting in the plugin_name |
147 | - being used. |
148 | + :param openlp.core.common.path.Path db_file_path: The file name to use for this database. Defaults to None |
149 | + resulting in the plugin_name being used. |
150 | + :param upgrade_mod: The upgrade_schema function for this database |
151 | """ |
152 | self.is_dirty = False |
153 | self.session = None |
154 | self.db_url = None |
155 | - if db_file_name: |
156 | + if db_file_path: |
157 | log.debug('Manager: Creating new DB url') |
158 | - self.db_url = init_url(plugin_name, db_file_name) |
159 | + self.db_url = init_url(plugin_name, str(db_file_path)) |
160 | else: |
161 | self.db_url = init_url(plugin_name) |
162 | if upgrade_mod: |
163 | try: |
164 | db_ver, up_ver = upgrade_db(self.db_url, upgrade_mod) |
165 | except (SQLAlchemyError, DBAPIError): |
166 | - handle_db_error(plugin_name, db_file_name) |
167 | + handle_db_error(plugin_name, str(db_file_path)) |
168 | return |
169 | if db_ver > up_ver: |
170 | critical_error_message_box( |
171 | @@ -322,7 +375,7 @@ |
172 | try: |
173 | self.session = init_schema(self.db_url) |
174 | except (SQLAlchemyError, DBAPIError): |
175 | - handle_db_error(plugin_name, db_file_name) |
176 | + handle_db_error(plugin_name, str(db_file_path)) |
177 | else: |
178 | self.session = session |
179 | |
180 | |
181 | === modified file 'openlp/core/ui/advancedtab.py' |
182 | --- openlp/core/ui/advancedtab.py 2017-08-23 20:21:11 +0000 |
183 | +++ openlp/core/ui/advancedtab.py 2017-09-25 17:10:35 +0000 |
184 | @@ -22,9 +22,8 @@ |
185 | """ |
186 | The :mod:`advancedtab` provides an advanced settings facility. |
187 | """ |
188 | +import logging |
189 | from datetime import datetime, timedelta |
190 | -import logging |
191 | -import os |
192 | |
193 | from PyQt5 import QtCore, QtGui, QtWidgets |
194 | |
195 | @@ -492,24 +491,27 @@ |
196 | self.service_name_edit.setText(UiStrings().DefaultServiceName) |
197 | self.service_name_edit.setFocus() |
198 | |
199 | - def on_data_directory_path_edit_path_changed(self, new_data_path): |
200 | + def on_data_directory_path_edit_path_changed(self, new_path): |
201 | """ |
202 | - Browse for a new data directory location. |
203 | + Handle the `editPathChanged` signal of the data_directory_path_edit |
204 | + |
205 | + :param openlp.core.common.path.Path new_path: The new path |
206 | + :rtype: None |
207 | """ |
208 | # Make sure they want to change the data. |
209 | answer = QtWidgets.QMessageBox.question(self, translate('OpenLP.AdvancedTab', 'Confirm Data Directory Change'), |
210 | translate('OpenLP.AdvancedTab', 'Are you sure you want to change the ' |
211 | 'location of the OpenLP data directory to:\n\n{path}' |
212 | '\n\nThe data directory will be changed when OpenLP is ' |
213 | - 'closed.').format(path=new_data_path), |
214 | + 'closed.').format(path=new_path), |
215 | defaultButton=QtWidgets.QMessageBox.No) |
216 | if answer != QtWidgets.QMessageBox.Yes: |
217 | self.data_directory_path_edit.path = AppLocation.get_data_path() |
218 | return |
219 | # Check if data already exists here. |
220 | - self.check_data_overwrite(path_to_str(new_data_path)) |
221 | + self.check_data_overwrite(new_path) |
222 | # Save the new location. |
223 | - self.main_window.set_new_data_path(path_to_str(new_data_path)) |
224 | + self.main_window.new_data_path = new_path |
225 | self.data_directory_cancel_button.show() |
226 | |
227 | def on_data_directory_copy_check_box_toggled(self): |
228 | @@ -526,9 +528,10 @@ |
229 | def check_data_overwrite(self, data_path): |
230 | """ |
231 | Check if there's already data in the target directory. |
232 | + |
233 | + :param openlp.core.common.path.Path data_path: The target directory to check |
234 | """ |
235 | - test_path = os.path.join(data_path, 'songs') |
236 | - if os.path.exists(test_path): |
237 | + if (data_path / 'songs').exists(): |
238 | self.data_exists = True |
239 | # Check is they want to replace existing data. |
240 | answer = QtWidgets.QMessageBox.warning(self, |
241 | @@ -537,7 +540,7 @@ |
242 | 'WARNING: \n\nThe location you have selected \n\n{path}' |
243 | '\n\nappears to contain OpenLP data files. Do you wish to ' |
244 | 'replace these files with the current data ' |
245 | - 'files?').format(path=os.path.abspath(data_path,)), |
246 | + 'files?'.format(path=data_path)), |
247 | QtWidgets.QMessageBox.StandardButtons(QtWidgets.QMessageBox.Yes | |
248 | QtWidgets.QMessageBox.No), |
249 | QtWidgets.QMessageBox.No) |
250 | @@ -559,7 +562,7 @@ |
251 | """ |
252 | self.data_directory_path_edit.path = AppLocation.get_data_path() |
253 | self.data_directory_copy_check_box.setChecked(False) |
254 | - self.main_window.set_new_data_path(None) |
255 | + self.main_window.new_data_path = None |
256 | self.main_window.set_copy_data(False) |
257 | self.data_directory_copy_check_box.hide() |
258 | self.data_directory_cancel_button.hide() |
259 | |
260 | === modified file 'openlp/core/ui/exceptionform.py' |
261 | --- openlp/core/ui/exceptionform.py 2017-08-26 15:06:11 +0000 |
262 | +++ openlp/core/ui/exceptionform.py 2017-09-25 17:10:35 +0000 |
263 | @@ -149,21 +149,11 @@ |
264 | opts = self._create_report() |
265 | report_text = self.report_text.format(version=opts['version'], description=opts['description'], |
266 | traceback=opts['traceback'], libs=opts['libs'], system=opts['system']) |
267 | - filename = str(file_path) |
268 | try: |
269 | - report_file = open(filename, 'w') |
270 | - try: |
271 | + with file_path.open('w') as report_file: |
272 | report_file.write(report_text) |
273 | - except UnicodeError: |
274 | - report_file.close() |
275 | - report_file = open(filename, 'wb') |
276 | - report_file.write(report_text.encode('utf-8')) |
277 | - finally: |
278 | - report_file.close() |
279 | except IOError: |
280 | log.exception('Failed to write crash report') |
281 | - finally: |
282 | - report_file.close() |
283 | |
284 | def on_send_report_button_clicked(self): |
285 | """ |
286 | @@ -219,7 +209,7 @@ |
287 | translate('ImagePlugin.ExceptionDialog', 'Select Attachment'), |
288 | Settings().value(self.settings_section + '/last directory'), |
289 | '{text} (*)'.format(text=UiStrings().AllFiles)) |
290 | - log.info('New file {file}'.format(file=file_path)) |
291 | + log.info('New files {file_path}'.format(file_path=file_path)) |
292 | if file_path: |
293 | self.file_attachment = str(file_path) |
294 | |
295 | |
296 | === modified file 'openlp/core/ui/lib/filedialog.py' |
297 | --- openlp/core/ui/lib/filedialog.py 2017-09-20 20:44:57 +0000 |
298 | +++ openlp/core/ui/lib/filedialog.py 2017-09-25 17:10:35 +0000 |
299 | @@ -31,11 +31,11 @@ |
300 | """ |
301 | Wraps `getExistingDirectory` so that it can be called with, and return Path objects |
302 | |
303 | - :type parent: QtWidgets.QWidget or None |
304 | + :type parent: QtWidgets.QWidget | None |
305 | :type caption: str |
306 | :type directory: openlp.core.common.path.Path |
307 | :type options: QtWidgets.QFileDialog.Options |
308 | - :rtype: tuple[Path, str] |
309 | + :rtype: tuple[openlp.core.common.path.Path, str] |
310 | """ |
311 | args, kwargs = replace_params(args, kwargs, ((2, 'directory', path_to_str),)) |
312 | |
313 | @@ -50,13 +50,13 @@ |
314 | """ |
315 | Wraps `getOpenFileName` so that it can be called with, and return Path objects |
316 | |
317 | - :type parent: QtWidgets.QWidget or None |
318 | + :type parent: QtWidgets.QWidget | None |
319 | :type caption: str |
320 | :type directory: openlp.core.common.path.Path |
321 | :type filter: str |
322 | :type initialFilter: str |
323 | :type options: QtWidgets.QFileDialog.Options |
324 | - :rtype: tuple[Path, str] |
325 | + :rtype: tuple[openlp.core.common.path.Path, str] |
326 | """ |
327 | args, kwargs = replace_params(args, kwargs, ((2, 'directory', path_to_str),)) |
328 | |
329 | @@ -71,13 +71,13 @@ |
330 | """ |
331 | Wraps `getOpenFileNames` so that it can be called with, and return Path objects |
332 | |
333 | - :type parent: QtWidgets.QWidget or None |
334 | + :type parent: QtWidgets.QWidget | None |
335 | :type caption: str |
336 | :type directory: openlp.core.common.path.Path |
337 | :type filter: str |
338 | :type initialFilter: str |
339 | :type options: QtWidgets.QFileDialog.Options |
340 | - :rtype: tuple[list[Path], str] |
341 | + :rtype: tuple[list[openlp.core.common.path.Path], str] |
342 | """ |
343 | args, kwargs = replace_params(args, kwargs, ((2, 'directory', path_to_str),)) |
344 | |
345 | @@ -93,13 +93,13 @@ |
346 | """ |
347 | Wraps `getSaveFileName` so that it can be called with, and return Path objects |
348 | |
349 | - :type parent: QtWidgets.QWidget or None |
350 | + :type parent: QtWidgets.QWidget | None |
351 | :type caption: str |
352 | :type directory: openlp.core.common.path.Path |
353 | :type filter: str |
354 | :type initialFilter: str |
355 | :type options: QtWidgets.QFileDialog.Options |
356 | - :rtype: tuple[Path or None, str] |
357 | + :rtype: tuple[openlp.core.common.path.Path | None, str] |
358 | """ |
359 | args, kwargs = replace_params(args, kwargs, ((2, 'directory', path_to_str),)) |
360 | |
361 | |
362 | === modified file 'openlp/core/ui/mainwindow.py' |
363 | --- openlp/core/ui/mainwindow.py 2017-09-20 20:44:57 +0000 |
364 | +++ openlp/core/ui/mainwindow.py 2017-09-25 17:10:35 +0000 |
365 | @@ -1332,12 +1332,6 @@ |
366 | if self.application: |
367 | self.application.process_events() |
368 | |
369 | - def set_new_data_path(self, new_data_path): |
370 | - """ |
371 | - Set the new data path |
372 | - """ |
373 | - self.new_data_path = new_data_path |
374 | - |
375 | def set_copy_data(self, copy_data): |
376 | """ |
377 | Set the flag to copy the data |
378 | @@ -1349,7 +1343,7 @@ |
379 | Change the data directory. |
380 | """ |
381 | log.info('Changing data path to {newpath}'.format(newpath=self.new_data_path)) |
382 | - old_data_path = str(AppLocation.get_data_path()) |
383 | + old_data_path = AppLocation.get_data_path() |
384 | # Copy OpenLP data to new location if requested. |
385 | self.application.set_busy_cursor() |
386 | if self.copy_data: |
387 | @@ -1358,7 +1352,7 @@ |
388 | self.show_status_message( |
389 | translate('OpenLP.MainWindow', 'Copying OpenLP data to new data directory location - {path} ' |
390 | '- Please wait for copy to finish').format(path=self.new_data_path)) |
391 | - dir_util.copy_tree(old_data_path, self.new_data_path) |
392 | + dir_util.copy_tree(str(old_data_path), str(self.new_data_path)) |
393 | log.info('Copy successful') |
394 | except (IOError, os.error, DistutilsFileError) as why: |
395 | self.application.set_normal_cursor() |
396 | @@ -1373,9 +1367,9 @@ |
397 | log.info('No data copy requested') |
398 | # Change the location of data directory in config file. |
399 | settings = QtCore.QSettings() |
400 | - settings.setValue('advanced/data path', Path(self.new_data_path)) |
401 | + settings.setValue('advanced/data path', self.new_data_path) |
402 | # Check if the new data path is our default. |
403 | - if self.new_data_path == str(AppLocation.get_directory(AppLocation.DataDir)): |
404 | + if self.new_data_path == AppLocation.get_directory(AppLocation.DataDir): |
405 | settings.remove('advanced/data path') |
406 | self.application.set_normal_cursor() |
407 | |
408 | |
409 | === modified file 'openlp/core/ui/servicemanager.py' |
410 | --- openlp/core/ui/servicemanager.py 2017-09-18 06:20:06 +0000 |
411 | +++ openlp/core/ui/servicemanager.py 2017-09-25 17:10:35 +0000 |
412 | @@ -376,7 +376,7 @@ |
413 | self._file_name = path_to_str(file_path) |
414 | self.main_window.set_service_modified(self.is_modified(), self.short_file_name()) |
415 | Settings().setValue('servicemanager/last file', file_path) |
416 | - if file_path and file_path.suffix() == '.oszl': |
417 | + if file_path and file_path.suffix == '.oszl': |
418 | self._save_lite = True |
419 | else: |
420 | self._save_lite = False |
421 | @@ -699,13 +699,15 @@ |
422 | default_file_name = format_time(default_pattern, local_time) |
423 | else: |
424 | default_file_name = '' |
425 | + default_file_path = Path(default_file_name) |
426 | directory_path = Settings().value(self.main_window.service_manager_settings_section + '/last directory') |
427 | - file_path = directory_path / default_file_name |
428 | + if directory_path: |
429 | + default_file_path = directory_path / default_file_path |
430 | # SaveAs from osz to oszl is not valid as the files will be deleted on exit which is not sensible or usable in |
431 | # the long term. |
432 | if self._file_name.endswith('oszl') or self.service_has_all_original_files: |
433 | file_path, filter_used = FileDialog.getSaveFileName( |
434 | - self.main_window, UiStrings().SaveService, file_path, |
435 | + self.main_window, UiStrings().SaveService, default_file_path, |
436 | translate('OpenLP.ServiceManager', |
437 | 'OpenLP Service Files (*.osz);; OpenLP Service Files - lite (*.oszl)')) |
438 | else: |
439 | |
440 | === modified file 'openlp/plugins/alerts/forms/alertform.py' |
441 | --- openlp/plugins/alerts/forms/alertform.py 2017-06-09 06:06:49 +0000 |
442 | +++ openlp/plugins/alerts/forms/alertform.py 2017-09-25 17:10:35 +0000 |
443 | @@ -70,7 +70,7 @@ |
444 | item_name = QtWidgets.QListWidgetItem(alert.text) |
445 | item_name.setData(QtCore.Qt.UserRole, alert.id) |
446 | self.alert_list_widget.addItem(item_name) |
447 | - if alert.text == str(self.alert_text_edit.text()): |
448 | + if alert.text == self.alert_text_edit.text(): |
449 | self.item_id = alert.id |
450 | self.alert_list_widget.setCurrentRow(self.alert_list_widget.row(item_name)) |
451 | |
452 | |
453 | === modified file 'openlp/plugins/alerts/lib/alertstab.py' |
454 | --- openlp/plugins/alerts/lib/alertstab.py 2017-08-03 17:54:40 +0000 |
455 | +++ openlp/plugins/alerts/lib/alertstab.py 2017-09-25 17:10:35 +0000 |
456 | @@ -32,9 +32,6 @@ |
457 | """ |
458 | AlertsTab is the alerts settings tab in the settings dialog. |
459 | """ |
460 | - def __init__(self, parent, name, visible_title, icon_path): |
461 | - super(AlertsTab, self).__init__(parent, name, visible_title, icon_path) |
462 | - |
463 | def setupUi(self): |
464 | self.setObjectName('AlertsTab') |
465 | super(AlertsTab, self).setupUi() |
466 | |
467 | === modified file 'openlp/plugins/custom/lib/customtab.py' |
468 | --- openlp/plugins/custom/lib/customtab.py 2016-12-31 11:01:36 +0000 |
469 | +++ openlp/plugins/custom/lib/customtab.py 2017-09-25 17:10:35 +0000 |
470 | @@ -34,9 +34,6 @@ |
471 | """ |
472 | CustomTab is the Custom settings tab in the settings dialog. |
473 | """ |
474 | - def __init__(self, parent, title, visible_title, icon_path): |
475 | - super(CustomTab, self).__init__(parent, title, visible_title, icon_path) |
476 | - |
477 | def setupUi(self): |
478 | self.setObjectName('CustomTab') |
479 | super(CustomTab, self).setupUi() |
480 | |
481 | === modified file 'openlp/plugins/images/imageplugin.py' |
482 | --- openlp/plugins/images/imageplugin.py 2017-09-09 20:00:48 +0000 |
483 | +++ openlp/plugins/images/imageplugin.py 2017-09-25 17:10:35 +0000 |
484 | @@ -29,7 +29,7 @@ |
485 | from openlp.core.lib import Plugin, StringContent, ImageSource, build_icon |
486 | from openlp.core.lib.db import Manager |
487 | from openlp.plugins.images.endpoint import api_images_endpoint, images_endpoint |
488 | -from openlp.plugins.images.lib import ImageMediaItem, ImageTab |
489 | +from openlp.plugins.images.lib import ImageMediaItem, ImageTab, upgrade |
490 | from openlp.plugins.images.lib.db import init_schema |
491 | |
492 | log = logging.getLogger(__name__) |
493 | @@ -50,7 +50,7 @@ |
494 | |
495 | def __init__(self): |
496 | super(ImagePlugin, self).__init__('images', __default_settings__, ImageMediaItem, ImageTab) |
497 | - self.manager = Manager('images', init_schema) |
498 | + self.manager = Manager('images', init_schema, upgrade_mod=upgrade) |
499 | self.weight = -7 |
500 | self.icon_path = ':/plugins/plugin_images.png' |
501 | self.icon = build_icon(self.icon_path) |
502 | |
503 | === modified file 'openlp/plugins/images/lib/db.py' |
504 | --- openlp/plugins/images/lib/db.py 2016-12-31 11:01:36 +0000 |
505 | +++ openlp/plugins/images/lib/db.py 2017-09-25 17:10:35 +0000 |
506 | @@ -22,11 +22,10 @@ |
507 | """ |
508 | The :mod:`db` module provides the database and schema that is the backend for the Images plugin. |
509 | """ |
510 | - |
511 | from sqlalchemy import Column, ForeignKey, Table, types |
512 | from sqlalchemy.orm import mapper |
513 | |
514 | -from openlp.core.lib.db import BaseModel, init_db |
515 | +from openlp.core.lib.db import BaseModel, PathType, init_db |
516 | |
517 | |
518 | class ImageGroups(BaseModel): |
519 | @@ -65,7 +64,7 @@ |
520 | |
521 | * id |
522 | * group_id |
523 | - * filename |
524 | + * file_path |
525 | """ |
526 | session, metadata = init_db(url) |
527 | |
528 | @@ -80,7 +79,7 @@ |
529 | image_filenames_table = Table('image_filenames', metadata, |
530 | Column('id', types.Integer(), primary_key=True), |
531 | Column('group_id', types.Integer(), ForeignKey('image_groups.id'), default=None), |
532 | - Column('filename', types.Unicode(255), nullable=False) |
533 | + Column('file_path', PathType(), nullable=False) |
534 | ) |
535 | |
536 | mapper(ImageGroups, image_groups_table) |
537 | |
538 | === modified file 'openlp/plugins/images/lib/imagetab.py' |
539 | --- openlp/plugins/images/lib/imagetab.py 2016-12-31 11:01:36 +0000 |
540 | +++ openlp/plugins/images/lib/imagetab.py 2017-09-25 17:10:35 +0000 |
541 | @@ -31,9 +31,6 @@ |
542 | """ |
543 | ImageTab is the images settings tab in the settings dialog. |
544 | """ |
545 | - def __init__(self, parent, name, visible_title, icon_path): |
546 | - super(ImageTab, self).__init__(parent, name, visible_title, icon_path) |
547 | - |
548 | def setupUi(self): |
549 | self.setObjectName('ImagesTab') |
550 | super(ImageTab, self).setupUi() |
551 | |
552 | === modified file 'openlp/plugins/images/lib/mediaitem.py' |
553 | --- openlp/plugins/images/lib/mediaitem.py 2017-09-17 19:43:15 +0000 |
554 | +++ openlp/plugins/images/lib/mediaitem.py 2017-09-25 17:10:35 +0000 |
555 | @@ -21,7 +21,6 @@ |
556 | ############################################################################### |
557 | |
558 | import logging |
559 | -import os |
560 | |
561 | from PyQt5 import QtCore, QtGui, QtWidgets |
562 | |
563 | @@ -99,11 +98,11 @@ |
564 | self.list_view.setIconSize(QtCore.QSize(88, 50)) |
565 | self.list_view.setIndentation(self.list_view.default_indentation) |
566 | self.list_view.allow_internal_dnd = True |
567 | - self.service_path = os.path.join(str(AppLocation.get_section_data_path(self.settings_section)), 'thumbnails') |
568 | - check_directory_exists(Path(self.service_path)) |
569 | + self.service_path = AppLocation.get_section_data_path(self.settings_section) / 'thumbnails' |
570 | + check_directory_exists(self.service_path) |
571 | # Load images from the database |
572 | self.load_full_list( |
573 | - self.manager.get_all_objects(ImageFilenames, order_by_ref=ImageFilenames.filename), initial_load=True) |
574 | + self.manager.get_all_objects(ImageFilenames, order_by_ref=ImageFilenames.file_path), initial_load=True) |
575 | |
576 | def add_list_view_to_toolbar(self): |
577 | """ |
578 | @@ -211,8 +210,8 @@ |
579 | """ |
580 | images = self.manager.get_all_objects(ImageFilenames, ImageFilenames.group_id == image_group.id) |
581 | for image in images: |
582 | - delete_file(Path(self.service_path, os.path.split(image.filename)[1])) |
583 | - delete_file(Path(self.generate_thumbnail_path(image))) |
584 | + delete_file(self.service_path / image.file_path.name) |
585 | + delete_file(self.generate_thumbnail_path(image)) |
586 | self.manager.delete_object(ImageFilenames, image.id) |
587 | image_groups = self.manager.get_all_objects(ImageGroups, ImageGroups.parent_id == image_group.id) |
588 | for group in image_groups: |
589 | @@ -234,8 +233,8 @@ |
590 | if row_item: |
591 | item_data = row_item.data(0, QtCore.Qt.UserRole) |
592 | if isinstance(item_data, ImageFilenames): |
593 | - delete_file(Path(self.service_path, row_item.text(0))) |
594 | - delete_file(Path(self.generate_thumbnail_path(item_data))) |
595 | + delete_file(self.service_path / row_item.text(0)) |
596 | + delete_file(self.generate_thumbnail_path(item_data)) |
597 | if item_data.group_id == 0: |
598 | self.list_view.takeTopLevelItem(self.list_view.indexOfTopLevelItem(row_item)) |
599 | else: |
600 | @@ -326,17 +325,19 @@ |
601 | """ |
602 | Generate a path to the thumbnail |
603 | |
604 | - :param image: An instance of ImageFileNames |
605 | - :return: A path to the thumbnail of type str |
606 | + :param openlp.plugins.images.lib.db.ImageFilenames image: The image to generate the thumbnail path for. |
607 | + :return: A path to the thumbnail |
608 | + :rtype: openlp.core.common.path.Path |
609 | """ |
610 | - ext = os.path.splitext(image.filename)[1].lower() |
611 | - return os.path.join(self.service_path, '{}{}'.format(str(image.id), ext)) |
612 | + ext = image.file_path.suffix.lower() |
613 | + return self.service_path / '{name:d}{ext}'.format(name=image.id, ext=ext) |
614 | |
615 | def load_full_list(self, images, initial_load=False, open_group=None): |
616 | """ |
617 | Replace the list of images and groups in the interface. |
618 | |
619 | - :param images: A List of Image Filenames objects that will be used to reload the mediamanager list. |
620 | + :param list[openlp.plugins.images.lib.db.ImageFilenames] images: A List of Image Filenames objects that will be |
621 | + used to reload the mediamanager list. |
622 | :param initial_load: When set to False, the busy cursor and progressbar will be shown while loading images. |
623 | :param open_group: ImageGroups object of the group that must be expanded after reloading the list in the |
624 | interface. |
625 | @@ -352,34 +353,34 @@ |
626 | self.expand_group(open_group.id) |
627 | # Sort the images by its filename considering language specific. |
628 | # characters. |
629 | - images.sort(key=lambda image_object: get_locale_key(os.path.split(str(image_object.filename))[1])) |
630 | - for image_file in images: |
631 | - log.debug('Loading image: {name}'.format(name=image_file.filename)) |
632 | - filename = os.path.split(image_file.filename)[1] |
633 | - thumb = self.generate_thumbnail_path(image_file) |
634 | - if not os.path.exists(image_file.filename): |
635 | + images.sort(key=lambda image_object: get_locale_key(image_object.file_path.name)) |
636 | + for image in images: |
637 | + log.debug('Loading image: {name}'.format(name=image.file_path)) |
638 | + file_name = image.file_path.name |
639 | + thumbnail_path = self.generate_thumbnail_path(image) |
640 | + if not image.file_path.exists(): |
641 | icon = build_icon(':/general/general_delete.png') |
642 | else: |
643 | - if validate_thumb(Path(image_file.filename), Path(thumb)): |
644 | - icon = build_icon(thumb) |
645 | + if validate_thumb(image.file_path, thumbnail_path): |
646 | + icon = build_icon(thumbnail_path) |
647 | else: |
648 | - icon = create_thumb(image_file.filename, thumb) |
649 | - item_name = QtWidgets.QTreeWidgetItem([filename]) |
650 | - item_name.setText(0, filename) |
651 | + icon = create_thumb(image.file_path, thumbnail_path) |
652 | + item_name = QtWidgets.QTreeWidgetItem([file_name]) |
653 | + item_name.setText(0, file_name) |
654 | item_name.setIcon(0, icon) |
655 | - item_name.setToolTip(0, image_file.filename) |
656 | - item_name.setData(0, QtCore.Qt.UserRole, image_file) |
657 | - if image_file.group_id == 0: |
658 | + item_name.setToolTip(0, str(image.file_path)) |
659 | + item_name.setData(0, QtCore.Qt.UserRole, image) |
660 | + if image.group_id == 0: |
661 | self.list_view.addTopLevelItem(item_name) |
662 | else: |
663 | - group_items[image_file.group_id].addChild(item_name) |
664 | + group_items[image.group_id].addChild(item_name) |
665 | if not initial_load: |
666 | self.main_window.increment_progress_bar() |
667 | if not initial_load: |
668 | self.main_window.finished_progress_bar() |
669 | self.application.set_normal_cursor() |
670 | |
671 | - def validate_and_load(self, files, target_group=None): |
672 | + def validate_and_load(self, file_paths, target_group=None): |
673 | """ |
674 | Process a list for files either from the File Dialog or from Drag and Drop. |
675 | This method is overloaded from MediaManagerItem. |
676 | @@ -388,15 +389,15 @@ |
677 | :param target_group: The QTreeWidgetItem of the group that will be the parent of the added files |
678 | """ |
679 | self.application.set_normal_cursor() |
680 | - self.load_list(files, target_group) |
681 | - last_dir = os.path.split(files[0])[0] |
682 | - Settings().setValue(self.settings_section + '/last directory', Path(last_dir)) |
683 | + self.load_list(file_paths, target_group) |
684 | + last_dir = file_paths[0].parent |
685 | + Settings().setValue(self.settings_section + '/last directory', last_dir) |
686 | |
687 | - def load_list(self, images, target_group=None, initial_load=False): |
688 | + def load_list(self, image_paths, target_group=None, initial_load=False): |
689 | """ |
690 | Add new images to the database. This method is called when adding images using the Add button or DnD. |
691 | |
692 | - :param images: A List of strings containing the filenames of the files to be loaded |
693 | + :param list[openlp.core.common.Path] image_paths: A list of file paths to the images to be loaded |
694 | :param target_group: The QTreeWidgetItem of the group that will be the parent of the added files |
695 | :param initial_load: When set to False, the busy cursor and progressbar will be shown while loading images |
696 | """ |
697 | @@ -429,7 +430,7 @@ |
698 | else: |
699 | self.choose_group_form.existing_radio_button.setDisabled(False) |
700 | self.choose_group_form.group_combobox.setDisabled(False) |
701 | - # Ask which group the images should be saved in |
702 | + # Ask which group the image_paths should be saved in |
703 | if self.choose_group_form.exec(selected_group=preselect_group): |
704 | if self.choose_group_form.nogroup_radio_button.isChecked(): |
705 | # User chose 'No group' |
706 | @@ -461,33 +462,33 @@ |
707 | return |
708 | # Initialize busy cursor and progress bar |
709 | self.application.set_busy_cursor() |
710 | - self.main_window.display_progress_bar(len(images)) |
711 | - # Save the new images in the database |
712 | - self.save_new_images_list(images, group_id=parent_group.id, reload_list=False) |
713 | - self.load_full_list(self.manager.get_all_objects(ImageFilenames, order_by_ref=ImageFilenames.filename), |
714 | + self.main_window.display_progress_bar(len(image_paths)) |
715 | + # Save the new image_paths in the database |
716 | + self.save_new_images_list(image_paths, group_id=parent_group.id, reload_list=False) |
717 | + self.load_full_list(self.manager.get_all_objects(ImageFilenames, order_by_ref=ImageFilenames.file_path), |
718 | initial_load=initial_load, open_group=parent_group) |
719 | self.application.set_normal_cursor() |
720 | |
721 | - def save_new_images_list(self, images_list, group_id=0, reload_list=True): |
722 | + def save_new_images_list(self, image_paths, group_id=0, reload_list=True): |
723 | """ |
724 | Convert a list of image filenames to ImageFilenames objects and save them in the database. |
725 | |
726 | - :param images_list: A List of strings containing image filenames |
727 | + :param list[Path] image_paths: A List of file paths to image |
728 | :param group_id: The ID of the group to save the images in |
729 | :param reload_list: This boolean is set to True when the list in the interface should be reloaded after saving |
730 | the new images |
731 | """ |
732 | - for filename in images_list: |
733 | - if not isinstance(filename, str): |
734 | + for image_path in image_paths: |
735 | + if not isinstance(image_path, Path): |
736 | continue |
737 | - log.debug('Adding new image: {name}'.format(name=filename)) |
738 | + log.debug('Adding new image: {name}'.format(name=image_path)) |
739 | image_file = ImageFilenames() |
740 | image_file.group_id = group_id |
741 | - image_file.filename = str(filename) |
742 | + image_file.file_path = image_path |
743 | self.manager.save_object(image_file) |
744 | self.main_window.increment_progress_bar() |
745 | - if reload_list and images_list: |
746 | - self.load_full_list(self.manager.get_all_objects(ImageFilenames, order_by_ref=ImageFilenames.filename)) |
747 | + if reload_list and image_paths: |
748 | + self.load_full_list(self.manager.get_all_objects(ImageFilenames, order_by_ref=ImageFilenames.file_path)) |
749 | |
750 | def dnd_move_internal(self, target): |
751 | """ |
752 | @@ -581,8 +582,8 @@ |
753 | return False |
754 | # Find missing files |
755 | for image in images: |
756 | - if not os.path.exists(image.filename): |
757 | - missing_items_file_names.append(image.filename) |
758 | + if not image.file_path.exists(): |
759 | + missing_items_file_names.append(str(image.file_path)) |
760 | # We cannot continue, as all images do not exist. |
761 | if not images: |
762 | if not remote: |
763 | @@ -601,9 +602,9 @@ |
764 | return False |
765 | # Continue with the existing images. |
766 | for image in images: |
767 | - name = os.path.split(image.filename)[1] |
768 | - thumbnail = self.generate_thumbnail_path(image) |
769 | - service_item.add_from_image(image.filename, name, background, thumbnail) |
770 | + name = image.file_path.name |
771 | + thumbnail_path = self.generate_thumbnail_path(image) |
772 | + service_item.add_from_image(str(image.file_path), name, background, str(thumbnail_path)) |
773 | return True |
774 | |
775 | def check_group_exists(self, new_group): |
776 | @@ -640,7 +641,7 @@ |
777 | if not self.check_group_exists(new_group): |
778 | if self.manager.save_object(new_group): |
779 | self.load_full_list(self.manager.get_all_objects( |
780 | - ImageFilenames, order_by_ref=ImageFilenames.filename)) |
781 | + ImageFilenames, order_by_ref=ImageFilenames.file_path)) |
782 | self.expand_group(new_group.id) |
783 | self.fill_groups_combobox(self.choose_group_form.group_combobox) |
784 | self.fill_groups_combobox(self.add_group_form.parent_group_combobox) |
785 | @@ -675,9 +676,9 @@ |
786 | if not isinstance(bitem.data(0, QtCore.Qt.UserRole), ImageFilenames): |
787 | # Only continue when an image is selected. |
788 | return |
789 | - filename = bitem.data(0, QtCore.Qt.UserRole).filename |
790 | - if os.path.exists(filename): |
791 | - if self.live_controller.display.direct_image(filename, background): |
792 | + file_path = bitem.data(0, QtCore.Qt.UserRole).file_path |
793 | + if file_path.exists(): |
794 | + if self.live_controller.display.direct_image(str(file_path), background): |
795 | self.reset_action.setVisible(True) |
796 | else: |
797 | critical_error_message_box( |
798 | @@ -687,22 +688,22 @@ |
799 | critical_error_message_box( |
800 | UiStrings().LiveBGError, |
801 | translate('ImagePlugin.MediaItem', 'There was a problem replacing your background, ' |
802 | - 'the image file "{name}" no longer exists.').format(name=filename)) |
803 | + 'the image file "{name}" no longer exists.').format(name=file_path)) |
804 | |
805 | def search(self, string, show_error=True): |
806 | """ |
807 | Perform a search on the image file names. |
808 | |
809 | - :param string: The glob to search for |
810 | - :param show_error: Unused. |
811 | + :param str string: The glob to search for |
812 | + :param bool show_error: Unused. |
813 | """ |
814 | files = self.manager.get_all_objects( |
815 | - ImageFilenames, filter_clause=ImageFilenames.filename.contains(string), |
816 | - order_by_ref=ImageFilenames.filename) |
817 | + ImageFilenames, filter_clause=ImageFilenames.file_path.contains(string), |
818 | + order_by_ref=ImageFilenames.file_path) |
819 | results = [] |
820 | for file_object in files: |
821 | - filename = os.path.split(str(file_object.filename))[1] |
822 | - results.append([file_object.filename, filename]) |
823 | + file_name = file_object.file_path.name |
824 | + results.append([str(file_object.file_path), file_name]) |
825 | return results |
826 | |
827 | def create_item_from_id(self, item_id): |
828 | @@ -711,8 +712,9 @@ |
829 | |
830 | :param item_id: Id to make live |
831 | """ |
832 | + item_id = Path(item_id) |
833 | item = QtWidgets.QTreeWidgetItem() |
834 | - item_data = self.manager.get_object_filtered(ImageFilenames, ImageFilenames.filename == item_id) |
835 | - item.setText(0, os.path.basename(item_data.filename)) |
836 | + item_data = self.manager.get_object_filtered(ImageFilenames, ImageFilenames.file_path == item_id) |
837 | + item.setText(0, item_data.file_path.name) |
838 | item.setData(0, QtCore.Qt.UserRole, item_data) |
839 | return item |
840 | |
841 | === added file 'openlp/plugins/images/lib/upgrade.py' |
842 | --- openlp/plugins/images/lib/upgrade.py 1970-01-01 00:00:00 +0000 |
843 | +++ openlp/plugins/images/lib/upgrade.py 2017-09-25 17:10:35 +0000 |
844 | @@ -0,0 +1,70 @@ |
845 | +# -*- coding: utf-8 -*- |
846 | +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 |
847 | + |
848 | +############################################################################### |
849 | +# OpenLP - Open Source Lyrics Projection # |
850 | +# --------------------------------------------------------------------------- # |
851 | +# Copyright (c) 2008-2017 OpenLP Developers # |
852 | +# --------------------------------------------------------------------------- # |
853 | +# This program is free software; you can redistribute it and/or modify it # |
854 | +# under the terms of the GNU General Public License as published by the Free # |
855 | +# Software Foundation; version 2 of the License. # |
856 | +# # |
857 | +# This program is distributed in the hope that it will be useful, but WITHOUT # |
858 | +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # |
859 | +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # |
860 | +# more details. # |
861 | +# # |
862 | +# You should have received a copy of the GNU General Public License along # |
863 | +# with this program; if not, write to the Free Software Foundation, Inc., 59 # |
864 | +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # |
865 | +############################################################################### |
866 | +""" |
867 | +The :mod:`upgrade` module provides the migration path for the OLP Paths database |
868 | +""" |
869 | +import json |
870 | +import logging |
871 | + |
872 | +from sqlalchemy import Column, Table |
873 | + |
874 | +from openlp.core.common import AppLocation |
875 | +from openlp.core.common.db import drop_columns |
876 | +from openlp.core.common.json import OpenLPJsonEncoder |
877 | +from openlp.core.common.path import Path |
878 | +from openlp.core.lib.db import PathType, get_upgrade_op |
879 | + |
880 | +log = logging.getLogger(__name__) |
881 | +__version__ = 2 |
882 | + |
883 | + |
884 | +def upgrade_1(session, metadata): |
885 | + """ |
886 | + Version 1 upgrade - old db might/might not be versioned. |
887 | + """ |
888 | + log.debug('Skipping upgrade_1 of files DB - not used') |
889 | + |
890 | + |
891 | +def upgrade_2(session, metadata): |
892 | + """ |
893 | + Version 2 upgrade - Move file path from old db to JSON encoded path to new db. Added during 2.5 dev |
894 | + """ |
895 | + # TODO: Update tests |
896 | + log.debug('Starting upgrade_2 for file_path to JSON') |
897 | + old_table = Table('image_filenames', metadata, autoload=True) |
898 | + if 'file_path' not in [col.name for col in old_table.c.values()]: |
899 | + op = get_upgrade_op(session) |
900 | + op.add_column('image_filenames', Column('file_path', PathType())) |
901 | + conn = op.get_bind() |
902 | + results = conn.execute('SELECT * FROM image_filenames') |
903 | + data_path = AppLocation.get_data_path() |
904 | + for row in results.fetchall(): |
905 | + file_path_json = json.dumps(Path(row.filename), cls=OpenLPJsonEncoder, base_path=data_path) |
906 | + sql = 'UPDATE image_filenames SET file_path = \'{file_path_json}\' WHERE id = {id}'.format( |
907 | + file_path_json=file_path_json, id=row.id) |
908 | + conn.execute(sql) |
909 | + # Drop old columns |
910 | + if metadata.bind.url.get_dialect().name == 'sqlite': |
911 | + drop_columns(op, 'image_filenames', ['filename', ]) |
912 | + else: |
913 | + op.drop_constraint('image_filenames', 'foreignkey') |
914 | + op.drop_column('image_filenames', 'filenames') |
915 | |
916 | === modified file 'openlp/plugins/songusage/forms/songusagedetaildialog.py' |
917 | --- openlp/plugins/songusage/forms/songusagedetaildialog.py 2017-06-09 06:06:49 +0000 |
918 | +++ openlp/plugins/songusage/forms/songusagedetaildialog.py 2017-09-25 17:10:35 +0000 |
919 | @@ -19,7 +19,6 @@ |
920 | # with this program; if not, write to the Free Software Foundation, Inc., 59 # |
921 | # Temple Place, Suite 330, Boston, MA 02111-1307 USA # |
922 | ############################################################################### |
923 | - |
924 | from PyQt5 import QtCore, QtWidgets |
925 | |
926 | from openlp.core.common import translate |
927 | |
928 | === modified file 'openlp/plugins/songusage/forms/songusagedetailform.py' |
929 | --- openlp/plugins/songusage/forms/songusagedetailform.py 2017-08-26 15:06:11 +0000 |
930 | +++ openlp/plugins/songusage/forms/songusagedetailform.py 2017-09-25 17:10:35 +0000 |
931 | @@ -19,7 +19,6 @@ |
932 | # with this program; if not, write to the Free Software Foundation, Inc., 59 # |
933 | # Temple Place, Suite 330, Boston, MA 02111-1307 USA # |
934 | ############################################################################### |
935 | - |
936 | import logging |
937 | import os |
938 | |
939 | @@ -60,7 +59,7 @@ |
940 | |
941 | def on_report_path_edit_path_changed(self, file_path): |
942 | """ |
943 | - Called when the path in the `PathEdit` has changed |
944 | + Handle the `pathEditChanged` signal from report_path_edit |
945 | |
946 | :param openlp.core.common.path.Path file_path: The new path. |
947 | :rtype: None |
948 | @@ -72,7 +71,7 @@ |
949 | Ok was triggered so lets save the data and run the report |
950 | """ |
951 | log.debug('accept') |
952 | - path = path_to_str(self.report_path_edit.path) |
953 | + path = self.report_path_edit.path |
954 | if not path: |
955 | self.main_window.error_message( |
956 | translate('SongUsagePlugin.SongUsageDetailForm', 'Output Path Not Selected'), |
957 | @@ -80,7 +79,7 @@ |
958 | ' song usage report. \nPlease select an existing path on your computer.') |
959 | ) |
960 | return |
961 | - check_directory_exists(Path(path)) |
962 | + check_directory_exists(path) |
963 | file_name = translate('SongUsagePlugin.SongUsageDetailForm', |
964 | 'usage_detail_{old}_{new}.txt' |
965 | ).format(old=self.from_date_calendar.selectedDate().toString('ddMMyyyy'), |
966 | @@ -91,29 +90,25 @@ |
967 | SongUsageItem, and_(SongUsageItem.usagedate >= self.from_date_calendar.selectedDate().toPyDate(), |
968 | SongUsageItem.usagedate < self.to_date_calendar.selectedDate().toPyDate()), |
969 | [SongUsageItem.usagedate, SongUsageItem.usagetime]) |
970 | - report_file_name = os.path.join(path, file_name) |
971 | - file_handle = None |
972 | + report_file_name = path / file_name |
973 | try: |
974 | - file_handle = open(report_file_name, 'wb') |
975 | - for instance in usage: |
976 | - record = ('\"{date}\",\"{time}\",\"{title}\",\"{copyright}\",\"{ccli}\",\"{authors}\",' |
977 | - '\"{name}\",\"{source}\"\n').format(date=instance.usagedate, time=instance.usagetime, |
978 | - title=instance.title, copyright=instance.copyright, |
979 | - ccli=instance.ccl_number, authors=instance.authors, |
980 | - name=instance.plugin_name, source=instance.source) |
981 | - file_handle.write(record.encode('utf-8')) |
982 | - self.main_window.information_message( |
983 | - translate('SongUsagePlugin.SongUsageDetailForm', 'Report Creation'), |
984 | - translate('SongUsagePlugin.SongUsageDetailForm', |
985 | - 'Report \n{name} \nhas been successfully created. ').format(name=report_file_name) |
986 | - ) |
987 | + with report_file_name.open('wb') as file_handle: |
988 | + for instance in usage: |
989 | + record = ('\"{date}\",\"{time}\",\"{title}\",\"{copyright}\",\"{ccli}\",\"{authors}\",' |
990 | + '\"{name}\",\"{source}\"\n').format(date=instance.usagedate, time=instance.usagetime, |
991 | + title=instance.title, copyright=instance.copyright, |
992 | + ccli=instance.ccl_number, authors=instance.authors, |
993 | + name=instance.plugin_name, source=instance.source) |
994 | + file_handle.write(record.encode('utf-8')) |
995 | + self.main_window.information_message( |
996 | + translate('SongUsagePlugin.SongUsageDetailForm', 'Report Creation'), |
997 | + translate('SongUsagePlugin.SongUsageDetailForm', |
998 | + 'Report \n{name} \nhas been successfully created. ').format(name=report_file_name) |
999 | + ) |
1000 | except OSError as ose: |
1001 | log.exception('Failed to write out song usage records') |
1002 | critical_error_message_box(translate('SongUsagePlugin.SongUsageDetailForm', 'Report Creation Failed'), |
1003 | translate('SongUsagePlugin.SongUsageDetailForm', |
1004 | 'An error occurred while creating the report: {error}' |
1005 | ).format(error=ose.strerror)) |
1006 | - finally: |
1007 | - if file_handle: |
1008 | - file_handle.close() |
1009 | self.close() |
1010 | |
1011 | === modified file 'tests/functional/openlp_core_ui/test_exceptionform.py' |
1012 | --- tests/functional/openlp_core_ui/test_exceptionform.py 2017-09-07 21:52:39 +0000 |
1013 | +++ tests/functional/openlp_core_ui/test_exceptionform.py 2017-09-25 17:10:35 +0000 |
1014 | @@ -22,11 +22,11 @@ |
1015 | """ |
1016 | Package to test the openlp.core.ui.exeptionform package. |
1017 | """ |
1018 | - |
1019 | import os |
1020 | import tempfile |
1021 | + |
1022 | from unittest import TestCase |
1023 | -from unittest.mock import mock_open, patch |
1024 | +from unittest.mock import call, patch |
1025 | |
1026 | from openlp.core.common import Registry |
1027 | from openlp.core.common.path import Path |
1028 | @@ -142,15 +142,15 @@ |
1029 | test_form = exceptionform.ExceptionForm() |
1030 | test_form.file_attachment = None |
1031 | |
1032 | - with patch.object(test_form, '_pyuno_import') as mock_pyuno: |
1033 | - with patch.object(test_form.exception_text_edit, 'toPlainText') as mock_traceback: |
1034 | - with patch.object(test_form.description_text_edit, 'toPlainText') as mock_description: |
1035 | - mock_pyuno.return_value = 'UNO Bridge Test' |
1036 | - mock_traceback.return_value = 'openlp: Traceback Test' |
1037 | - mock_description.return_value = 'Description Test' |
1038 | + with patch.object(test_form, '_pyuno_import') as mock_pyuno, \ |
1039 | + patch.object(test_form.exception_text_edit, 'toPlainText') as mock_traceback, \ |
1040 | + patch.object(test_form.description_text_edit, 'toPlainText') as mock_description: |
1041 | + mock_pyuno.return_value = 'UNO Bridge Test' |
1042 | + mock_traceback.return_value = 'openlp: Traceback Test' |
1043 | + mock_description.return_value = 'Description Test' |
1044 | |
1045 | - # WHEN: on_save_report_button_clicked called |
1046 | - test_form.on_send_report_button_clicked() |
1047 | + # WHEN: on_save_report_button_clicked called |
1048 | + test_form.on_send_report_button_clicked() |
1049 | |
1050 | # THEN: Verify strings were formatted properly |
1051 | mocked_add_query_item.assert_called_with('body', MAIL_ITEM_TEXT) |
1052 | @@ -182,25 +182,24 @@ |
1053 | mocked_qt.PYQT_VERSION_STR = 'PyQt5 Test' |
1054 | mocked_is_linux.return_value = False |
1055 | mocked_application_version.return_value = 'Trunk Test' |
1056 | - mocked_save_filename.return_value = (Path('testfile.txt'), 'filter') |
1057 | - |
1058 | - test_form = exceptionform.ExceptionForm() |
1059 | - test_form.file_attachment = None |
1060 | - |
1061 | - with patch.object(test_form, '_pyuno_import') as mock_pyuno: |
1062 | - with patch.object(test_form.exception_text_edit, 'toPlainText') as mock_traceback: |
1063 | - with patch.object(test_form.description_text_edit, 'toPlainText') as mock_description: |
1064 | - with patch("openlp.core.ui.exceptionform.open", mock_open(), create=True) as mocked_open: |
1065 | - mock_pyuno.return_value = 'UNO Bridge Test' |
1066 | - mock_traceback.return_value = 'openlp: Traceback Test' |
1067 | - mock_description.return_value = 'Description Test' |
1068 | - |
1069 | - # WHEN: on_save_report_button_clicked called |
1070 | - test_form.on_save_report_button_clicked() |
1071 | + |
1072 | + with patch.object(Path, 'open') as mocked_path_open: |
1073 | + test_path = Path('testfile.txt') |
1074 | + mocked_save_filename.return_value = test_path, 'ext' |
1075 | + |
1076 | + test_form = exceptionform.ExceptionForm() |
1077 | + test_form.file_attachment = None |
1078 | + |
1079 | + with patch.object(test_form, '_pyuno_import') as mock_pyuno, \ |
1080 | + patch.object(test_form.exception_text_edit, 'toPlainText') as mock_traceback, \ |
1081 | + patch.object(test_form.description_text_edit, 'toPlainText') as mock_description: |
1082 | + mock_pyuno.return_value = 'UNO Bridge Test' |
1083 | + mock_traceback.return_value = 'openlp: Traceback Test' |
1084 | + mock_description.return_value = 'Description Test' |
1085 | + |
1086 | + # WHEN: on_save_report_button_clicked called |
1087 | + test_form.on_save_report_button_clicked() |
1088 | |
1089 | # THEN: Verify proper calls to save file |
1090 | # self.maxDiff = None |
1091 | - check_text = "call().write({text})".format(text=MAIL_ITEM_TEXT.__repr__()) |
1092 | - write_text = "{text}".format(text=mocked_open.mock_calls[1]) |
1093 | - mocked_open.assert_called_with('testfile.txt', 'w') |
1094 | - self.assertEquals(check_text, write_text, "Saved information should match test text") |
1095 | + mocked_path_open.assert_has_calls([call().__enter__().write(MAIL_ITEM_TEXT)]) |
1096 | |
1097 | === modified file 'tests/functional/openlp_plugins/images/test_lib.py' |
1098 | --- tests/functional/openlp_plugins/images/test_lib.py 2017-08-26 15:06:11 +0000 |
1099 | +++ tests/functional/openlp_plugins/images/test_lib.py 2017-09-25 17:10:35 +0000 |
1100 | @@ -58,7 +58,7 @@ |
1101 | Test that the validate_and_load_test() method when called without a group |
1102 | """ |
1103 | # GIVEN: A list of files |
1104 | - file_list = ['/path1/image1.jpg', '/path2/image2.jpg'] |
1105 | + file_list = [Path('path1', 'image1.jpg'), Path('path2', 'image2.jpg')] |
1106 | |
1107 | # WHEN: Calling validate_and_load with the list of files |
1108 | self.media_item.validate_and_load(file_list) |
1109 | @@ -66,7 +66,7 @@ |
1110 | # THEN: load_list should have been called with the file list and None, |
1111 | # the directory should have been saved to the settings |
1112 | mocked_load_list.assert_called_once_with(file_list, None) |
1113 | - mocked_settings().setValue.assert_called_once_with(ANY, Path('/', 'path1')) |
1114 | + mocked_settings().setValue.assert_called_once_with(ANY, Path('path1')) |
1115 | |
1116 | @patch('openlp.plugins.images.lib.mediaitem.ImageMediaItem.load_list') |
1117 | @patch('openlp.plugins.images.lib.mediaitem.Settings') |
1118 | @@ -75,7 +75,7 @@ |
1119 | Test that the validate_and_load_test() method when called with a group |
1120 | """ |
1121 | # GIVEN: A list of files |
1122 | - file_list = ['/path1/image1.jpg', '/path2/image2.jpg'] |
1123 | + file_list = [Path('path1', 'image1.jpg'), Path('path2', 'image2.jpg')] |
1124 | |
1125 | # WHEN: Calling validate_and_load with the list of files and a group |
1126 | self.media_item.validate_and_load(file_list, 'group') |
1127 | @@ -83,7 +83,7 @@ |
1128 | # THEN: load_list should have been called with the file list and the group name, |
1129 | # the directory should have been saved to the settings |
1130 | mocked_load_list.assert_called_once_with(file_list, 'group') |
1131 | - mocked_settings().setValue.assert_called_once_with(ANY, Path('/', 'path1')) |
1132 | + mocked_settings().setValue.assert_called_once_with(ANY, Path('path1')) |
1133 | |
1134 | @patch('openlp.plugins.images.lib.mediaitem.ImageMediaItem.load_full_list') |
1135 | def test_save_new_images_list_empty_list(self, mocked_load_full_list): |
1136 | @@ -107,8 +107,8 @@ |
1137 | Test that the save_new_images_list() calls load_full_list() when reload_list is set to True |
1138 | """ |
1139 | # GIVEN: A list with 1 image and a mocked out manager |
1140 | - image_list = ['test_image.jpg'] |
1141 | - ImageFilenames.filename = '' |
1142 | + image_list = [Path('test_image.jpg')] |
1143 | + ImageFilenames.file_path = None |
1144 | self.media_item.manager = MagicMock() |
1145 | |
1146 | # WHEN: We run save_new_images_list with reload_list=True |
1147 | @@ -118,7 +118,7 @@ |
1148 | self.assertEquals(mocked_load_full_list.call_count, 1, 'load_full_list() should have been called') |
1149 | |
1150 | # CLEANUP: Remove added attribute from ImageFilenames |
1151 | - delattr(ImageFilenames, 'filename') |
1152 | + delattr(ImageFilenames, 'file_path') |
1153 | |
1154 | @patch('openlp.plugins.images.lib.mediaitem.ImageMediaItem.load_full_list') |
1155 | def test_save_new_images_list_single_image_without_reload(self, mocked_load_full_list): |
1156 | @@ -126,7 +126,7 @@ |
1157 | Test that the save_new_images_list() doesn't call load_full_list() when reload_list is set to False |
1158 | """ |
1159 | # GIVEN: A list with 1 image and a mocked out manager |
1160 | - image_list = ['test_image.jpg'] |
1161 | + image_list = [Path('test_image.jpg')] |
1162 | self.media_item.manager = MagicMock() |
1163 | |
1164 | # WHEN: We run save_new_images_list with reload_list=False |
1165 | @@ -141,7 +141,7 @@ |
1166 | Test that the save_new_images_list() saves all images in the list |
1167 | """ |
1168 | # GIVEN: A list with 3 images |
1169 | - image_list = ['test_image_1.jpg', 'test_image_2.jpg', 'test_image_3.jpg'] |
1170 | + image_list = [Path('test_image_1.jpg'), Path('test_image_2.jpg'), Path('test_image_3.jpg')] |
1171 | self.media_item.manager = MagicMock() |
1172 | |
1173 | # WHEN: We run save_new_images_list with the list of 3 images |
1174 | @@ -157,7 +157,7 @@ |
1175 | Test that the save_new_images_list() ignores everything in the provided list except strings |
1176 | """ |
1177 | # GIVEN: A list with images and objects |
1178 | - image_list = ['test_image_1.jpg', None, True, ImageFilenames(), 'test_image_2.jpg'] |
1179 | + image_list = [Path('test_image_1.jpg'), None, True, ImageFilenames(), Path('test_image_2.jpg')] |
1180 | self.media_item.manager = MagicMock() |
1181 | |
1182 | # WHEN: We run save_new_images_list with the list of images and objects |
1183 | @@ -191,7 +191,7 @@ |
1184 | ImageGroups.parent_id = 1 |
1185 | self.media_item.manager = MagicMock() |
1186 | self.media_item.manager.get_all_objects.side_effect = self._recursively_delete_group_side_effect |
1187 | - self.media_item.service_path = '' |
1188 | + self.media_item.service_path = Path() |
1189 | test_group = ImageGroups() |
1190 | test_group.id = 1 |
1191 | |
1192 | @@ -215,13 +215,13 @@ |
1193 | # Create some fake objects that should be removed |
1194 | returned_object1 = ImageFilenames() |
1195 | returned_object1.id = 1 |
1196 | - returned_object1.filename = '/tmp/test_file_1.jpg' |
1197 | + returned_object1.file_path = Path('/', 'tmp', 'test_file_1.jpg') |
1198 | returned_object2 = ImageFilenames() |
1199 | returned_object2.id = 2 |
1200 | - returned_object2.filename = '/tmp/test_file_2.jpg' |
1201 | + returned_object2.file_path = Path('/', 'tmp', 'test_file_2.jpg') |
1202 | returned_object3 = ImageFilenames() |
1203 | returned_object3.id = 3 |
1204 | - returned_object3.filename = '/tmp/test_file_3.jpg' |
1205 | + returned_object3.file_path = Path('/', 'tmp', 'test_file_3.jpg') |
1206 | return [returned_object1, returned_object2, returned_object3] |
1207 | if args[1] == ImageGroups and args[2]: |
1208 | # Change the parent_id that is matched so we don't get into an endless loop |
1209 | @@ -243,9 +243,9 @@ |
1210 | test_image = ImageFilenames() |
1211 | test_image.id = 1 |
1212 | test_image.group_id = 1 |
1213 | - test_image.filename = 'imagefile.png' |
1214 | + test_image.file_path = Path('imagefile.png') |
1215 | self.media_item.manager = MagicMock() |
1216 | - self.media_item.service_path = '' |
1217 | + self.media_item.service_path = Path() |
1218 | self.media_item.list_view = MagicMock() |
1219 | mocked_row_item = MagicMock() |
1220 | mocked_row_item.data.return_value = test_image |
1221 | @@ -265,13 +265,13 @@ |
1222 | # GIVEN: An ImageFilenames that already exists in the database |
1223 | image_file = ImageFilenames() |
1224 | image_file.id = 1 |
1225 | - image_file.filename = '/tmp/test_file_1.jpg' |
1226 | + image_file.file_path = Path('/', 'tmp', 'test_file_1.jpg') |
1227 | self.media_item.manager = MagicMock() |
1228 | self.media_item.manager.get_object_filtered.return_value = image_file |
1229 | - ImageFilenames.filename = '' |
1230 | + ImageFilenames.file_path = None |
1231 | |
1232 | # WHEN: create_item_from_id() is called |
1233 | - item = self.media_item.create_item_from_id(1) |
1234 | + item = self.media_item.create_item_from_id('1') |
1235 | |
1236 | # THEN: A QTreeWidgetItem should be created with the above model object as it's data |
1237 | self.assertIsInstance(item, QtWidgets.QTreeWidgetItem) |
1238 | @@ -279,4 +279,4 @@ |
1239 | item_data = item.data(0, QtCore.Qt.UserRole) |
1240 | self.assertIsInstance(item_data, ImageFilenames) |
1241 | self.assertEqual(1, item_data.id) |
1242 | - self.assertEqual('/tmp/test_file_1.jpg', item_data.filename) |
1243 | + self.assertEqual(Path('/', 'tmp', 'test_file_1.jpg'), item_data.file_path) |
1244 | |
1245 | === added file 'tests/functional/openlp_plugins/images/test_upgrade.py' |
1246 | --- tests/functional/openlp_plugins/images/test_upgrade.py 1970-01-01 00:00:00 +0000 |
1247 | +++ tests/functional/openlp_plugins/images/test_upgrade.py 2017-09-25 17:10:35 +0000 |
1248 | @@ -0,0 +1,83 @@ |
1249 | +# -*- coding: utf-8 -*- |
1250 | +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 |
1251 | + |
1252 | +############################################################################### |
1253 | +# OpenLP - Open Source Lyrics Projection # |
1254 | +# --------------------------------------------------------------------------- # |
1255 | +# Copyright (c) 2008-2017 OpenLP Developers # |
1256 | +# --------------------------------------------------------------------------- # |
1257 | +# This program is free software; you can redistribute it and/or modify it # |
1258 | +# under the terms of the GNU General Public License as published by the Free # |
1259 | +# Software Foundation; version 2 of the License. # |
1260 | +# # |
1261 | +# This program is distributed in the hope that it will be useful, but WITHOUT # |
1262 | +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # |
1263 | +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # |
1264 | +# more details. # |
1265 | +# # |
1266 | +# You should have received a copy of the GNU General Public License along # |
1267 | +# with this program; if not, write to the Free Software Foundation, Inc., 59 # |
1268 | +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # |
1269 | +############################################################################### |
1270 | +""" |
1271 | +This module contains tests for the lib submodule of the Images plugin. |
1272 | +""" |
1273 | +import os |
1274 | +import shutil |
1275 | +from tempfile import mkdtemp |
1276 | +from unittest import TestCase |
1277 | +from unittest.mock import patch |
1278 | + |
1279 | +from openlp.core.common import AppLocation, Settings |
1280 | +from openlp.core.common.path import Path |
1281 | +from openlp.core.lib.db import Manager |
1282 | +from openlp.plugins.images.lib import upgrade |
1283 | +from openlp.plugins.images.lib.db import ImageFilenames, init_schema |
1284 | + |
1285 | +from tests.helpers.testmixin import TestMixin |
1286 | +from tests.utils.constants import TEST_RESOURCES_PATH |
1287 | + |
1288 | +__default_settings__ = { |
1289 | + 'images/db type': 'sqlite', |
1290 | + 'images/background color': '#000000', |
1291 | +} |
1292 | + |
1293 | + |
1294 | +class TestImageDBUpgrade(TestCase, TestMixin): |
1295 | + """ |
1296 | + Test that the image database is upgraded correctly |
1297 | + """ |
1298 | + def setUp(self): |
1299 | + self.build_settings() |
1300 | + Settings().extend_default_settings(__default_settings__) |
1301 | + self.tmp_folder = mkdtemp() |
1302 | + |
1303 | + def tearDown(self): |
1304 | + """ |
1305 | + Delete all the C++ objects at the end so that we don't have a segfault |
1306 | + """ |
1307 | + self.destroy_settings() |
1308 | + # Ignore errors since windows can have problems with locked files |
1309 | + shutil.rmtree(self.tmp_folder, ignore_errors=True) |
1310 | + |
1311 | + def test_image_filenames_table(self): |
1312 | + """ |
1313 | + Test that the ImageFilenames table is correctly upgraded to the latest version |
1314 | + """ |
1315 | + # GIVEN: An unversioned image database |
1316 | + temp_db_name = os.path.join(self.tmp_folder, 'image-v0.sqlite') |
1317 | + shutil.copyfile(os.path.join(TEST_RESOURCES_PATH, 'images', 'image-v0.sqlite'), temp_db_name) |
1318 | + |
1319 | + with patch.object(AppLocation, 'get_data_path', return_value=Path('/', 'test', 'dir')): |
1320 | + # WHEN: Initalising the database manager |
1321 | + manager = Manager('images', init_schema, db_file_path=temp_db_name, upgrade_mod=upgrade) |
1322 | + |
1323 | + # THEN: The database should have been upgraded and image_filenames.file_path should return Path objects |
1324 | + upgraded_results = manager.get_all_objects(ImageFilenames) |
1325 | + |
1326 | + expected_result_data = {1: Path('/', 'test', 'image1.jpg'), |
1327 | + 2: Path('/', 'test', 'dir', 'image2.jpg'), |
1328 | + 3: Path('/', 'test', 'dir', 'subdir', 'image3.jpg')} |
1329 | + |
1330 | + for result in upgraded_results: |
1331 | + self.assertEqual(expected_result_data[result.id], result.file_path) |
1332 | |
1333 | === added directory 'tests/resources/images' |
1334 | === added file 'tests/resources/images/image-v0.sqlite' |
1335 | Binary files tests/resources/images/image-v0.sqlite 1970-01-01 00:00:00 +0000 and tests/resources/images/image-v0.sqlite 2017-09-25 17:10:35 +0000 differ |
Why have you removed a couple of __init__ methods?