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

Proposed by Phill
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
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.

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://ci.openlp.io/job/Branch-01-Pull/2207/
[SUCCESS] https://ci.openlp.io/job/Branch-02-Functional-Tests/2110/
[SUCCESS] https://ci.openlp.io/job/Branch-03-Interface-Tests/1997/
[SUCCESS] https://ci.openlp.io/job/Branch-04a-Code_Analysis/1364/
[SUCCESS] https://ci.openlp.io/job/Branch-04b-Test_Coverage/1196/
[SUCCESS] https://ci.openlp.io/job/Branch-04c-Code_Analysis2/326/
[FAILURE] https://ci.openlp.io/job/Branch-05-AppVeyor-Tests/165/
Stopping after failure

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

Why have you removed a couple of __init__ methods?

review: Needs Information
Revision history for this message
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://code.launchpad.net/~phill-ridout/openlp/pathlib6/+merge/331252
> You are the owner of lp:~phill-ridout/openlp/pathlib6.
>

Revision history for this message
Phill (phill-ridout) wrote : Posted in a previous version of this proposal

Just noticed one minor mistake.

review: Needs Fixing
Revision history for this message
Tim Bentley (trb143) :
review: Approve
Revision history for this message
Tomas Groth (tomasgroth) wrote :

Just a minor thing, see inline comment.

review: Needs Fixing
Revision history for this message
Tomas Groth (tomasgroth) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'openlp/core/common/httputils.py'
--- openlp/core/common/httputils.py 2017-09-06 20:18:08 +0000
+++ openlp/core/common/httputils.py 2017-09-25 17:10:35 +0000
@@ -24,7 +24,6 @@
24"""24"""
25import hashlib25import hashlib
26import logging26import logging
27import os
28import platform27import platform
29import socket28import socket
30import sys29import sys
3130
=== modified file 'openlp/core/lib/db.py'
--- openlp/core/lib/db.py 2017-08-12 17:45:56 +0000
+++ openlp/core/lib/db.py 2017-09-25 17:10:35 +0000
@@ -23,12 +23,13 @@
23"""23"""
24The :mod:`db` module provides the core database functionality for OpenLP24The :mod:`db` module provides the core database functionality for OpenLP
25"""25"""
26import json
26import logging27import logging
27import os28import os
28from copy import copy29from copy import copy
29from urllib.parse import quote_plus as urlquote30from urllib.parse import quote_plus as urlquote
3031
31from sqlalchemy import Table, MetaData, Column, types, create_engine32from sqlalchemy import Table, MetaData, Column, types, create_engine, UnicodeText
32from sqlalchemy.engine.url import make_url33from sqlalchemy.engine.url import make_url
33from sqlalchemy.exc import SQLAlchemyError, InvalidRequestError, DBAPIError, OperationalError, ProgrammingError34from sqlalchemy.exc import SQLAlchemyError, InvalidRequestError, DBAPIError, OperationalError, ProgrammingError
34from sqlalchemy.orm import scoped_session, sessionmaker, mapper35from sqlalchemy.orm import scoped_session, sessionmaker, mapper
@@ -37,7 +38,8 @@
37from alembic.migration import MigrationContext38from alembic.migration import MigrationContext
38from alembic.operations import Operations39from alembic.operations import Operations
3940
40from openlp.core.common import AppLocation, Settings, translate, delete_file41from openlp.core.common import AppLocation, Settings, delete_file, translate
42from openlp.core.common.json import OpenLPJsonDecoder, OpenLPJsonEncoder
41from openlp.core.lib.ui import critical_error_message_box43from openlp.core.lib.ui import critical_error_message_box
4244
43log = logging.getLogger(__name__)45log = logging.getLogger(__name__)
@@ -133,9 +135,10 @@
133 if db_file_name is None:135 if db_file_name is None:
134 return 'sqlite:///{path}/{plugin}.sqlite'.format(path=AppLocation.get_section_data_path(plugin_name),136 return 'sqlite:///{path}/{plugin}.sqlite'.format(path=AppLocation.get_section_data_path(plugin_name),
135 plugin=plugin_name)137 plugin=plugin_name)
138 elif os.path.isabs(db_file_name):
139 return 'sqlite:///{db_file_name}'.format(db_file_name=db_file_name)
136 else:140 else:
137 return 'sqlite:///{path}/{name}'.format(path=AppLocation.get_section_data_path(plugin_name),141 return 'sqlite:///{path}/{name}'.format(path=AppLocation.get_section_data_path(plugin_name), name=db_file_name)
138 name=db_file_name)
139142
140143
141def handle_db_error(plugin_name, db_file_name):144def handle_db_error(plugin_name, db_file_name):
@@ -200,6 +203,55 @@
200 return instance203 return instance
201204
202205
206class PathType(types.TypeDecorator):
207 """
208 Create a PathType for storing Path objects with SQLAlchemy. Behind the scenes we convert the Path object to a JSON
209 representation and store it as a Unicode type
210 """
211 impl = types.UnicodeText
212
213 def coerce_compared_value(self, op, value):
214 """
215 Some times it make sense to compare a PathType with a string. In the case a string is used coerce the the
216 PathType to a UnicodeText type.
217
218 :param op: The operation being carried out. Not used, as we only care about the type that is being used with the
219 operation.
220 :param openlp.core.common.path.Path | str value: The value being used for the comparison. Most likely a Path
221 Object or str.
222 :return: The coerced value stored in the db
223 :rtype: PathType or UnicodeText
224 """
225 if isinstance(value, str):
226 return UnicodeText()
227 else:
228 return self
229
230 def process_bind_param(self, value, dialect):
231 """
232 Convert the Path object to a JSON representation
233
234 :param openlp.core.common.path.Path value: The value to convert
235 :param dialect: Not used
236 :return: The Path object as a JSON string
237 :rtype: str
238 """
239 data_path = AppLocation.get_data_path()
240 return json.dumps(value, cls=OpenLPJsonEncoder, base_path=data_path)
241
242 def process_result_value(self, value, dialect):
243 """
244 Convert the JSON representation back
245
246 :param types.UnicodeText value: The value to convert
247 :param dialect: Not used
248 :return: The JSON object converted Python object (in this case it should be a Path object)
249 :rtype: openlp.core.common.path.Path
250 """
251 data_path = AppLocation.get_data_path()
252 return json.loads(value, cls=OpenLPJsonDecoder, base_path=data_path)
253
254
203def upgrade_db(url, upgrade):255def upgrade_db(url, upgrade):
204 """256 """
205 Upgrade a database.257 Upgrade a database.
@@ -208,7 +260,7 @@
208 :param upgrade: The python module that contains the upgrade instructions.260 :param upgrade: The python module that contains the upgrade instructions.
209 """261 """
210 if not database_exists(url):262 if not database_exists(url):
211 log.warn("Database {db} doesn't exist - skipping upgrade checks".format(db=url))263 log.warning("Database {db} doesn't exist - skipping upgrade checks".format(db=url))
212 return (0, 0)264 return (0, 0)
213265
214 log.debug('Checking upgrades for DB {db}'.format(db=url))266 log.debug('Checking upgrades for DB {db}'.format(db=url))
@@ -273,10 +325,11 @@
273 :param plugin_name: The name of the plugin to remove the database for325 :param plugin_name: The name of the plugin to remove the database for
274 :param db_file_name: The database file name. Defaults to None resulting in the plugin_name being used.326 :param db_file_name: The database file name. Defaults to None resulting in the plugin_name being used.
275 """327 """
328 db_file_path = AppLocation.get_section_data_path(plugin_name)
276 if db_file_name:329 if db_file_name:
277 db_file_path = AppLocation.get_section_data_path(plugin_name) / db_file_name330 db_file_path = db_file_path / db_file_name
278 else:331 else:
279 db_file_path = AppLocation.get_section_data_path(plugin_name) / plugin_name332 db_file_path = db_file_path / plugin_name
280 return delete_file(db_file_path)333 return delete_file(db_file_path)
281334
282335
@@ -284,30 +337,30 @@
284 """337 """
285 Provide generic object persistence management338 Provide generic object persistence management
286 """339 """
287 def __init__(self, plugin_name, init_schema, db_file_name=None, upgrade_mod=None, session=None):340 def __init__(self, plugin_name, init_schema, db_file_path=None, upgrade_mod=None, session=None):
288 """341 """
289 Runs the initialisation process that includes creating the connection to the database and the tables if they do342 Runs the initialisation process that includes creating the connection to the database and the tables if they do
290 not exist.343 not exist.
291344
292 :param plugin_name: The name to setup paths and settings section names345 :param plugin_name: The name to setup paths and settings section names
293 :param init_schema: The init_schema function for this database346 :param init_schema: The init_schema function for this database
294 :param db_file_name: The upgrade_schema function for this database347 :param openlp.core.common.path.Path db_file_path: The file name to use for this database. Defaults to None
295 :param upgrade_mod: The file name to use for this database. Defaults to None resulting in the plugin_name348 resulting in the plugin_name being used.
296 being used.349 :param upgrade_mod: The upgrade_schema function for this database
297 """350 """
298 self.is_dirty = False351 self.is_dirty = False
299 self.session = None352 self.session = None
300 self.db_url = None353 self.db_url = None
301 if db_file_name:354 if db_file_path:
302 log.debug('Manager: Creating new DB url')355 log.debug('Manager: Creating new DB url')
303 self.db_url = init_url(plugin_name, db_file_name)356 self.db_url = init_url(plugin_name, str(db_file_path))
304 else:357 else:
305 self.db_url = init_url(plugin_name)358 self.db_url = init_url(plugin_name)
306 if upgrade_mod:359 if upgrade_mod:
307 try:360 try:
308 db_ver, up_ver = upgrade_db(self.db_url, upgrade_mod)361 db_ver, up_ver = upgrade_db(self.db_url, upgrade_mod)
309 except (SQLAlchemyError, DBAPIError):362 except (SQLAlchemyError, DBAPIError):
310 handle_db_error(plugin_name, db_file_name)363 handle_db_error(plugin_name, str(db_file_path))
311 return364 return
312 if db_ver > up_ver:365 if db_ver > up_ver:
313 critical_error_message_box(366 critical_error_message_box(
@@ -322,7 +375,7 @@
322 try:375 try:
323 self.session = init_schema(self.db_url)376 self.session = init_schema(self.db_url)
324 except (SQLAlchemyError, DBAPIError):377 except (SQLAlchemyError, DBAPIError):
325 handle_db_error(plugin_name, db_file_name)378 handle_db_error(plugin_name, str(db_file_path))
326 else:379 else:
327 self.session = session380 self.session = session
328381
329382
=== modified file 'openlp/core/ui/advancedtab.py'
--- openlp/core/ui/advancedtab.py 2017-08-23 20:21:11 +0000
+++ openlp/core/ui/advancedtab.py 2017-09-25 17:10:35 +0000
@@ -22,9 +22,8 @@
22"""22"""
23The :mod:`advancedtab` provides an advanced settings facility.23The :mod:`advancedtab` provides an advanced settings facility.
24"""24"""
25import logging
25from datetime import datetime, timedelta26from datetime import datetime, timedelta
26import logging
27import os
2827
29from PyQt5 import QtCore, QtGui, QtWidgets28from PyQt5 import QtCore, QtGui, QtWidgets
3029
@@ -492,24 +491,27 @@
492 self.service_name_edit.setText(UiStrings().DefaultServiceName)491 self.service_name_edit.setText(UiStrings().DefaultServiceName)
493 self.service_name_edit.setFocus()492 self.service_name_edit.setFocus()
494493
495 def on_data_directory_path_edit_path_changed(self, new_data_path):494 def on_data_directory_path_edit_path_changed(self, new_path):
496 """495 """
497 Browse for a new data directory location.496 Handle the `editPathChanged` signal of the data_directory_path_edit
497
498 :param openlp.core.common.path.Path new_path: The new path
499 :rtype: None
498 """500 """
499 # Make sure they want to change the data.501 # Make sure they want to change the data.
500 answer = QtWidgets.QMessageBox.question(self, translate('OpenLP.AdvancedTab', 'Confirm Data Directory Change'),502 answer = QtWidgets.QMessageBox.question(self, translate('OpenLP.AdvancedTab', 'Confirm Data Directory Change'),
501 translate('OpenLP.AdvancedTab', 'Are you sure you want to change the '503 translate('OpenLP.AdvancedTab', 'Are you sure you want to change the '
502 'location of the OpenLP data directory to:\n\n{path}'504 'location of the OpenLP data directory to:\n\n{path}'
503 '\n\nThe data directory will be changed when OpenLP is '505 '\n\nThe data directory will be changed when OpenLP is '
504 'closed.').format(path=new_data_path),506 'closed.').format(path=new_path),
505 defaultButton=QtWidgets.QMessageBox.No)507 defaultButton=QtWidgets.QMessageBox.No)
506 if answer != QtWidgets.QMessageBox.Yes:508 if answer != QtWidgets.QMessageBox.Yes:
507 self.data_directory_path_edit.path = AppLocation.get_data_path()509 self.data_directory_path_edit.path = AppLocation.get_data_path()
508 return510 return
509 # Check if data already exists here.511 # Check if data already exists here.
510 self.check_data_overwrite(path_to_str(new_data_path))512 self.check_data_overwrite(new_path)
511 # Save the new location.513 # Save the new location.
512 self.main_window.set_new_data_path(path_to_str(new_data_path))514 self.main_window.new_data_path = new_path
513 self.data_directory_cancel_button.show()515 self.data_directory_cancel_button.show()
514516
515 def on_data_directory_copy_check_box_toggled(self):517 def on_data_directory_copy_check_box_toggled(self):
@@ -526,9 +528,10 @@
526 def check_data_overwrite(self, data_path):528 def check_data_overwrite(self, data_path):
527 """529 """
528 Check if there's already data in the target directory.530 Check if there's already data in the target directory.
531
532 :param openlp.core.common.path.Path data_path: The target directory to check
529 """533 """
530 test_path = os.path.join(data_path, 'songs')534 if (data_path / 'songs').exists():
531 if os.path.exists(test_path):
532 self.data_exists = True535 self.data_exists = True
533 # Check is they want to replace existing data.536 # Check is they want to replace existing data.
534 answer = QtWidgets.QMessageBox.warning(self,537 answer = QtWidgets.QMessageBox.warning(self,
@@ -537,7 +540,7 @@
537 'WARNING: \n\nThe location you have selected \n\n{path}'540 'WARNING: \n\nThe location you have selected \n\n{path}'
538 '\n\nappears to contain OpenLP data files. Do you wish to '541 '\n\nappears to contain OpenLP data files. Do you wish to '
539 'replace these files with the current data '542 'replace these files with the current data '
540 'files?').format(path=os.path.abspath(data_path,)),543 'files?'.format(path=data_path)),
541 QtWidgets.QMessageBox.StandardButtons(QtWidgets.QMessageBox.Yes |544 QtWidgets.QMessageBox.StandardButtons(QtWidgets.QMessageBox.Yes |
542 QtWidgets.QMessageBox.No),545 QtWidgets.QMessageBox.No),
543 QtWidgets.QMessageBox.No)546 QtWidgets.QMessageBox.No)
@@ -559,7 +562,7 @@
559 """562 """
560 self.data_directory_path_edit.path = AppLocation.get_data_path()563 self.data_directory_path_edit.path = AppLocation.get_data_path()
561 self.data_directory_copy_check_box.setChecked(False)564 self.data_directory_copy_check_box.setChecked(False)
562 self.main_window.set_new_data_path(None)565 self.main_window.new_data_path = None
563 self.main_window.set_copy_data(False)566 self.main_window.set_copy_data(False)
564 self.data_directory_copy_check_box.hide()567 self.data_directory_copy_check_box.hide()
565 self.data_directory_cancel_button.hide()568 self.data_directory_cancel_button.hide()
566569
=== modified file 'openlp/core/ui/exceptionform.py'
--- openlp/core/ui/exceptionform.py 2017-08-26 15:06:11 +0000
+++ openlp/core/ui/exceptionform.py 2017-09-25 17:10:35 +0000
@@ -149,21 +149,11 @@
149 opts = self._create_report()149 opts = self._create_report()
150 report_text = self.report_text.format(version=opts['version'], description=opts['description'],150 report_text = self.report_text.format(version=opts['version'], description=opts['description'],
151 traceback=opts['traceback'], libs=opts['libs'], system=opts['system'])151 traceback=opts['traceback'], libs=opts['libs'], system=opts['system'])
152 filename = str(file_path)
153 try:152 try:
154 report_file = open(filename, 'w')153 with file_path.open('w') as report_file:
155 try:
156 report_file.write(report_text)154 report_file.write(report_text)
157 except UnicodeError:
158 report_file.close()
159 report_file = open(filename, 'wb')
160 report_file.write(report_text.encode('utf-8'))
161 finally:
162 report_file.close()
163 except IOError:155 except IOError:
164 log.exception('Failed to write crash report')156 log.exception('Failed to write crash report')
165 finally:
166 report_file.close()
167157
168 def on_send_report_button_clicked(self):158 def on_send_report_button_clicked(self):
169 """159 """
@@ -219,7 +209,7 @@
219 translate('ImagePlugin.ExceptionDialog', 'Select Attachment'),209 translate('ImagePlugin.ExceptionDialog', 'Select Attachment'),
220 Settings().value(self.settings_section + '/last directory'),210 Settings().value(self.settings_section + '/last directory'),
221 '{text} (*)'.format(text=UiStrings().AllFiles))211 '{text} (*)'.format(text=UiStrings().AllFiles))
222 log.info('New file {file}'.format(file=file_path))212 log.info('New files {file_path}'.format(file_path=file_path))
223 if file_path:213 if file_path:
224 self.file_attachment = str(file_path)214 self.file_attachment = str(file_path)
225215
226216
=== modified file 'openlp/core/ui/lib/filedialog.py'
--- openlp/core/ui/lib/filedialog.py 2017-09-20 20:44:57 +0000
+++ openlp/core/ui/lib/filedialog.py 2017-09-25 17:10:35 +0000
@@ -31,11 +31,11 @@
31 """31 """
32 Wraps `getExistingDirectory` so that it can be called with, and return Path objects32 Wraps `getExistingDirectory` so that it can be called with, and return Path objects
3333
34 :type parent: QtWidgets.QWidget or None34 :type parent: QtWidgets.QWidget | None
35 :type caption: str35 :type caption: str
36 :type directory: openlp.core.common.path.Path36 :type directory: openlp.core.common.path.Path
37 :type options: QtWidgets.QFileDialog.Options37 :type options: QtWidgets.QFileDialog.Options
38 :rtype: tuple[Path, str]38 :rtype: tuple[openlp.core.common.path.Path, str]
39 """39 """
40 args, kwargs = replace_params(args, kwargs, ((2, 'directory', path_to_str),))40 args, kwargs = replace_params(args, kwargs, ((2, 'directory', path_to_str),))
4141
@@ -50,13 +50,13 @@
50 """50 """
51 Wraps `getOpenFileName` so that it can be called with, and return Path objects51 Wraps `getOpenFileName` so that it can be called with, and return Path objects
5252
53 :type parent: QtWidgets.QWidget or None53 :type parent: QtWidgets.QWidget | None
54 :type caption: str54 :type caption: str
55 :type directory: openlp.core.common.path.Path55 :type directory: openlp.core.common.path.Path
56 :type filter: str56 :type filter: str
57 :type initialFilter: str57 :type initialFilter: str
58 :type options: QtWidgets.QFileDialog.Options58 :type options: QtWidgets.QFileDialog.Options
59 :rtype: tuple[Path, str]59 :rtype: tuple[openlp.core.common.path.Path, str]
60 """60 """
61 args, kwargs = replace_params(args, kwargs, ((2, 'directory', path_to_str),))61 args, kwargs = replace_params(args, kwargs, ((2, 'directory', path_to_str),))
6262
@@ -71,13 +71,13 @@
71 """71 """
72 Wraps `getOpenFileNames` so that it can be called with, and return Path objects72 Wraps `getOpenFileNames` so that it can be called with, and return Path objects
7373
74 :type parent: QtWidgets.QWidget or None74 :type parent: QtWidgets.QWidget | None
75 :type caption: str75 :type caption: str
76 :type directory: openlp.core.common.path.Path76 :type directory: openlp.core.common.path.Path
77 :type filter: str77 :type filter: str
78 :type initialFilter: str78 :type initialFilter: str
79 :type options: QtWidgets.QFileDialog.Options79 :type options: QtWidgets.QFileDialog.Options
80 :rtype: tuple[list[Path], str]80 :rtype: tuple[list[openlp.core.common.path.Path], str]
81 """81 """
82 args, kwargs = replace_params(args, kwargs, ((2, 'directory', path_to_str),))82 args, kwargs = replace_params(args, kwargs, ((2, 'directory', path_to_str),))
8383
@@ -93,13 +93,13 @@
93 """93 """
94 Wraps `getSaveFileName` so that it can be called with, and return Path objects94 Wraps `getSaveFileName` so that it can be called with, and return Path objects
9595
96 :type parent: QtWidgets.QWidget or None96 :type parent: QtWidgets.QWidget | None
97 :type caption: str97 :type caption: str
98 :type directory: openlp.core.common.path.Path98 :type directory: openlp.core.common.path.Path
99 :type filter: str99 :type filter: str
100 :type initialFilter: str100 :type initialFilter: str
101 :type options: QtWidgets.QFileDialog.Options101 :type options: QtWidgets.QFileDialog.Options
102 :rtype: tuple[Path or None, str]102 :rtype: tuple[openlp.core.common.path.Path | None, str]
103 """103 """
104 args, kwargs = replace_params(args, kwargs, ((2, 'directory', path_to_str),))104 args, kwargs = replace_params(args, kwargs, ((2, 'directory', path_to_str),))
105105
106106
=== modified file 'openlp/core/ui/mainwindow.py'
--- openlp/core/ui/mainwindow.py 2017-09-20 20:44:57 +0000
+++ openlp/core/ui/mainwindow.py 2017-09-25 17:10:35 +0000
@@ -1332,12 +1332,6 @@
1332 if self.application:1332 if self.application:
1333 self.application.process_events()1333 self.application.process_events()
13341334
1335 def set_new_data_path(self, new_data_path):
1336 """
1337 Set the new data path
1338 """
1339 self.new_data_path = new_data_path
1340
1341 def set_copy_data(self, copy_data):1335 def set_copy_data(self, copy_data):
1342 """1336 """
1343 Set the flag to copy the data1337 Set the flag to copy the data
@@ -1349,7 +1343,7 @@
1349 Change the data directory.1343 Change the data directory.
1350 """1344 """
1351 log.info('Changing data path to {newpath}'.format(newpath=self.new_data_path))1345 log.info('Changing data path to {newpath}'.format(newpath=self.new_data_path))
1352 old_data_path = str(AppLocation.get_data_path())1346 old_data_path = AppLocation.get_data_path()
1353 # Copy OpenLP data to new location if requested.1347 # Copy OpenLP data to new location if requested.
1354 self.application.set_busy_cursor()1348 self.application.set_busy_cursor()
1355 if self.copy_data:1349 if self.copy_data:
@@ -1358,7 +1352,7 @@
1358 self.show_status_message(1352 self.show_status_message(
1359 translate('OpenLP.MainWindow', 'Copying OpenLP data to new data directory location - {path} '1353 translate('OpenLP.MainWindow', 'Copying OpenLP data to new data directory location - {path} '
1360 '- Please wait for copy to finish').format(path=self.new_data_path))1354 '- Please wait for copy to finish').format(path=self.new_data_path))
1361 dir_util.copy_tree(old_data_path, self.new_data_path)1355 dir_util.copy_tree(str(old_data_path), str(self.new_data_path))
1362 log.info('Copy successful')1356 log.info('Copy successful')
1363 except (IOError, os.error, DistutilsFileError) as why:1357 except (IOError, os.error, DistutilsFileError) as why:
1364 self.application.set_normal_cursor()1358 self.application.set_normal_cursor()
@@ -1373,9 +1367,9 @@
1373 log.info('No data copy requested')1367 log.info('No data copy requested')
1374 # Change the location of data directory in config file.1368 # Change the location of data directory in config file.
1375 settings = QtCore.QSettings()1369 settings = QtCore.QSettings()
1376 settings.setValue('advanced/data path', Path(self.new_data_path))1370 settings.setValue('advanced/data path', self.new_data_path)
1377 # Check if the new data path is our default.1371 # Check if the new data path is our default.
1378 if self.new_data_path == str(AppLocation.get_directory(AppLocation.DataDir)):1372 if self.new_data_path == AppLocation.get_directory(AppLocation.DataDir):
1379 settings.remove('advanced/data path')1373 settings.remove('advanced/data path')
1380 self.application.set_normal_cursor()1374 self.application.set_normal_cursor()
13811375
13821376
=== modified file 'openlp/core/ui/servicemanager.py'
--- openlp/core/ui/servicemanager.py 2017-09-18 06:20:06 +0000
+++ openlp/core/ui/servicemanager.py 2017-09-25 17:10:35 +0000
@@ -376,7 +376,7 @@
376 self._file_name = path_to_str(file_path)376 self._file_name = path_to_str(file_path)
377 self.main_window.set_service_modified(self.is_modified(), self.short_file_name())377 self.main_window.set_service_modified(self.is_modified(), self.short_file_name())
378 Settings().setValue('servicemanager/last file', file_path)378 Settings().setValue('servicemanager/last file', file_path)
379 if file_path and file_path.suffix() == '.oszl':379 if file_path and file_path.suffix == '.oszl':
380 self._save_lite = True380 self._save_lite = True
381 else:381 else:
382 self._save_lite = False382 self._save_lite = False
@@ -699,13 +699,15 @@
699 default_file_name = format_time(default_pattern, local_time)699 default_file_name = format_time(default_pattern, local_time)
700 else:700 else:
701 default_file_name = ''701 default_file_name = ''
702 default_file_path = Path(default_file_name)
702 directory_path = Settings().value(self.main_window.service_manager_settings_section + '/last directory')703 directory_path = Settings().value(self.main_window.service_manager_settings_section + '/last directory')
703 file_path = directory_path / default_file_name704 if directory_path:
705 default_file_path = directory_path / default_file_path
704 # SaveAs from osz to oszl is not valid as the files will be deleted on exit which is not sensible or usable in706 # SaveAs from osz to oszl is not valid as the files will be deleted on exit which is not sensible or usable in
705 # the long term.707 # the long term.
706 if self._file_name.endswith('oszl') or self.service_has_all_original_files:708 if self._file_name.endswith('oszl') or self.service_has_all_original_files:
707 file_path, filter_used = FileDialog.getSaveFileName(709 file_path, filter_used = FileDialog.getSaveFileName(
708 self.main_window, UiStrings().SaveService, file_path,710 self.main_window, UiStrings().SaveService, default_file_path,
709 translate('OpenLP.ServiceManager',711 translate('OpenLP.ServiceManager',
710 'OpenLP Service Files (*.osz);; OpenLP Service Files - lite (*.oszl)'))712 'OpenLP Service Files (*.osz);; OpenLP Service Files - lite (*.oszl)'))
711 else:713 else:
712714
=== modified file 'openlp/plugins/alerts/forms/alertform.py'
--- openlp/plugins/alerts/forms/alertform.py 2017-06-09 06:06:49 +0000
+++ openlp/plugins/alerts/forms/alertform.py 2017-09-25 17:10:35 +0000
@@ -70,7 +70,7 @@
70 item_name = QtWidgets.QListWidgetItem(alert.text)70 item_name = QtWidgets.QListWidgetItem(alert.text)
71 item_name.setData(QtCore.Qt.UserRole, alert.id)71 item_name.setData(QtCore.Qt.UserRole, alert.id)
72 self.alert_list_widget.addItem(item_name)72 self.alert_list_widget.addItem(item_name)
73 if alert.text == str(self.alert_text_edit.text()):73 if alert.text == self.alert_text_edit.text():
74 self.item_id = alert.id74 self.item_id = alert.id
75 self.alert_list_widget.setCurrentRow(self.alert_list_widget.row(item_name))75 self.alert_list_widget.setCurrentRow(self.alert_list_widget.row(item_name))
7676
7777
=== modified file 'openlp/plugins/alerts/lib/alertstab.py'
--- openlp/plugins/alerts/lib/alertstab.py 2017-08-03 17:54:40 +0000
+++ openlp/plugins/alerts/lib/alertstab.py 2017-09-25 17:10:35 +0000
@@ -32,9 +32,6 @@
32 """32 """
33 AlertsTab is the alerts settings tab in the settings dialog.33 AlertsTab is the alerts settings tab in the settings dialog.
34 """34 """
35 def __init__(self, parent, name, visible_title, icon_path):
36 super(AlertsTab, self).__init__(parent, name, visible_title, icon_path)
37
38 def setupUi(self):35 def setupUi(self):
39 self.setObjectName('AlertsTab')36 self.setObjectName('AlertsTab')
40 super(AlertsTab, self).setupUi()37 super(AlertsTab, self).setupUi()
4138
=== modified file 'openlp/plugins/custom/lib/customtab.py'
--- openlp/plugins/custom/lib/customtab.py 2016-12-31 11:01:36 +0000
+++ openlp/plugins/custom/lib/customtab.py 2017-09-25 17:10:35 +0000
@@ -34,9 +34,6 @@
34 """34 """
35 CustomTab is the Custom settings tab in the settings dialog.35 CustomTab is the Custom settings tab in the settings dialog.
36 """36 """
37 def __init__(self, parent, title, visible_title, icon_path):
38 super(CustomTab, self).__init__(parent, title, visible_title, icon_path)
39
40 def setupUi(self):37 def setupUi(self):
41 self.setObjectName('CustomTab')38 self.setObjectName('CustomTab')
42 super(CustomTab, self).setupUi()39 super(CustomTab, self).setupUi()
4340
=== modified file 'openlp/plugins/images/imageplugin.py'
--- openlp/plugins/images/imageplugin.py 2017-09-09 20:00:48 +0000
+++ openlp/plugins/images/imageplugin.py 2017-09-25 17:10:35 +0000
@@ -29,7 +29,7 @@
29from openlp.core.lib import Plugin, StringContent, ImageSource, build_icon29from openlp.core.lib import Plugin, StringContent, ImageSource, build_icon
30from openlp.core.lib.db import Manager30from openlp.core.lib.db import Manager
31from openlp.plugins.images.endpoint import api_images_endpoint, images_endpoint31from openlp.plugins.images.endpoint import api_images_endpoint, images_endpoint
32from openlp.plugins.images.lib import ImageMediaItem, ImageTab32from openlp.plugins.images.lib import ImageMediaItem, ImageTab, upgrade
33from openlp.plugins.images.lib.db import init_schema33from openlp.plugins.images.lib.db import init_schema
3434
35log = logging.getLogger(__name__)35log = logging.getLogger(__name__)
@@ -50,7 +50,7 @@
5050
51 def __init__(self):51 def __init__(self):
52 super(ImagePlugin, self).__init__('images', __default_settings__, ImageMediaItem, ImageTab)52 super(ImagePlugin, self).__init__('images', __default_settings__, ImageMediaItem, ImageTab)
53 self.manager = Manager('images', init_schema)53 self.manager = Manager('images', init_schema, upgrade_mod=upgrade)
54 self.weight = -754 self.weight = -7
55 self.icon_path = ':/plugins/plugin_images.png'55 self.icon_path = ':/plugins/plugin_images.png'
56 self.icon = build_icon(self.icon_path)56 self.icon = build_icon(self.icon_path)
5757
=== modified file 'openlp/plugins/images/lib/db.py'
--- openlp/plugins/images/lib/db.py 2016-12-31 11:01:36 +0000
+++ openlp/plugins/images/lib/db.py 2017-09-25 17:10:35 +0000
@@ -22,11 +22,10 @@
22"""22"""
23The :mod:`db` module provides the database and schema that is the backend for the Images plugin.23The :mod:`db` module provides the database and schema that is the backend for the Images plugin.
24"""24"""
25
26from sqlalchemy import Column, ForeignKey, Table, types25from sqlalchemy import Column, ForeignKey, Table, types
27from sqlalchemy.orm import mapper26from sqlalchemy.orm import mapper
2827
29from openlp.core.lib.db import BaseModel, init_db28from openlp.core.lib.db import BaseModel, PathType, init_db
3029
3130
32class ImageGroups(BaseModel):31class ImageGroups(BaseModel):
@@ -65,7 +64,7 @@
6564
66 * id65 * id
67 * group_id66 * group_id
68 * filename67 * file_path
69 """68 """
70 session, metadata = init_db(url)69 session, metadata = init_db(url)
7170
@@ -80,7 +79,7 @@
80 image_filenames_table = Table('image_filenames', metadata,79 image_filenames_table = Table('image_filenames', metadata,
81 Column('id', types.Integer(), primary_key=True),80 Column('id', types.Integer(), primary_key=True),
82 Column('group_id', types.Integer(), ForeignKey('image_groups.id'), default=None),81 Column('group_id', types.Integer(), ForeignKey('image_groups.id'), default=None),
83 Column('filename', types.Unicode(255), nullable=False)82 Column('file_path', PathType(), nullable=False)
84 )83 )
8584
86 mapper(ImageGroups, image_groups_table)85 mapper(ImageGroups, image_groups_table)
8786
=== modified file 'openlp/plugins/images/lib/imagetab.py'
--- openlp/plugins/images/lib/imagetab.py 2016-12-31 11:01:36 +0000
+++ openlp/plugins/images/lib/imagetab.py 2017-09-25 17:10:35 +0000
@@ -31,9 +31,6 @@
31 """31 """
32 ImageTab is the images settings tab in the settings dialog.32 ImageTab is the images settings tab in the settings dialog.
33 """33 """
34 def __init__(self, parent, name, visible_title, icon_path):
35 super(ImageTab, self).__init__(parent, name, visible_title, icon_path)
36
37 def setupUi(self):34 def setupUi(self):
38 self.setObjectName('ImagesTab')35 self.setObjectName('ImagesTab')
39 super(ImageTab, self).setupUi()36 super(ImageTab, self).setupUi()
4037
=== modified file 'openlp/plugins/images/lib/mediaitem.py'
--- openlp/plugins/images/lib/mediaitem.py 2017-09-17 19:43:15 +0000
+++ openlp/plugins/images/lib/mediaitem.py 2017-09-25 17:10:35 +0000
@@ -21,7 +21,6 @@
21###############################################################################21###############################################################################
2222
23import logging23import logging
24import os
2524
26from PyQt5 import QtCore, QtGui, QtWidgets25from PyQt5 import QtCore, QtGui, QtWidgets
2726
@@ -99,11 +98,11 @@
99 self.list_view.setIconSize(QtCore.QSize(88, 50))98 self.list_view.setIconSize(QtCore.QSize(88, 50))
100 self.list_view.setIndentation(self.list_view.default_indentation)99 self.list_view.setIndentation(self.list_view.default_indentation)
101 self.list_view.allow_internal_dnd = True100 self.list_view.allow_internal_dnd = True
102 self.service_path = os.path.join(str(AppLocation.get_section_data_path(self.settings_section)), 'thumbnails')101 self.service_path = AppLocation.get_section_data_path(self.settings_section) / 'thumbnails'
103 check_directory_exists(Path(self.service_path))102 check_directory_exists(self.service_path)
104 # Load images from the database103 # Load images from the database
105 self.load_full_list(104 self.load_full_list(
106 self.manager.get_all_objects(ImageFilenames, order_by_ref=ImageFilenames.filename), initial_load=True)105 self.manager.get_all_objects(ImageFilenames, order_by_ref=ImageFilenames.file_path), initial_load=True)
107106
108 def add_list_view_to_toolbar(self):107 def add_list_view_to_toolbar(self):
109 """108 """
@@ -211,8 +210,8 @@
211 """210 """
212 images = self.manager.get_all_objects(ImageFilenames, ImageFilenames.group_id == image_group.id)211 images = self.manager.get_all_objects(ImageFilenames, ImageFilenames.group_id == image_group.id)
213 for image in images:212 for image in images:
214 delete_file(Path(self.service_path, os.path.split(image.filename)[1]))213 delete_file(self.service_path / image.file_path.name)
215 delete_file(Path(self.generate_thumbnail_path(image)))214 delete_file(self.generate_thumbnail_path(image))
216 self.manager.delete_object(ImageFilenames, image.id)215 self.manager.delete_object(ImageFilenames, image.id)
217 image_groups = self.manager.get_all_objects(ImageGroups, ImageGroups.parent_id == image_group.id)216 image_groups = self.manager.get_all_objects(ImageGroups, ImageGroups.parent_id == image_group.id)
218 for group in image_groups:217 for group in image_groups:
@@ -234,8 +233,8 @@
234 if row_item:233 if row_item:
235 item_data = row_item.data(0, QtCore.Qt.UserRole)234 item_data = row_item.data(0, QtCore.Qt.UserRole)
236 if isinstance(item_data, ImageFilenames):235 if isinstance(item_data, ImageFilenames):
237 delete_file(Path(self.service_path, row_item.text(0)))236 delete_file(self.service_path / row_item.text(0))
238 delete_file(Path(self.generate_thumbnail_path(item_data)))237 delete_file(self.generate_thumbnail_path(item_data))
239 if item_data.group_id == 0:238 if item_data.group_id == 0:
240 self.list_view.takeTopLevelItem(self.list_view.indexOfTopLevelItem(row_item))239 self.list_view.takeTopLevelItem(self.list_view.indexOfTopLevelItem(row_item))
241 else:240 else:
@@ -326,17 +325,19 @@
326 """325 """
327 Generate a path to the thumbnail326 Generate a path to the thumbnail
328327
329 :param image: An instance of ImageFileNames328 :param openlp.plugins.images.lib.db.ImageFilenames image: The image to generate the thumbnail path for.
330 :return: A path to the thumbnail of type str329 :return: A path to the thumbnail
330 :rtype: openlp.core.common.path.Path
331 """331 """
332 ext = os.path.splitext(image.filename)[1].lower()332 ext = image.file_path.suffix.lower()
333 return os.path.join(self.service_path, '{}{}'.format(str(image.id), ext))333 return self.service_path / '{name:d}{ext}'.format(name=image.id, ext=ext)
334334
335 def load_full_list(self, images, initial_load=False, open_group=None):335 def load_full_list(self, images, initial_load=False, open_group=None):
336 """336 """
337 Replace the list of images and groups in the interface.337 Replace the list of images and groups in the interface.
338338
339 :param images: A List of Image Filenames objects that will be used to reload the mediamanager list.339 :param list[openlp.plugins.images.lib.db.ImageFilenames] images: A List of Image Filenames objects that will be
340 used to reload the mediamanager list.
340 :param initial_load: When set to False, the busy cursor and progressbar will be shown while loading images.341 :param initial_load: When set to False, the busy cursor and progressbar will be shown while loading images.
341 :param open_group: ImageGroups object of the group that must be expanded after reloading the list in the342 :param open_group: ImageGroups object of the group that must be expanded after reloading the list in the
342 interface.343 interface.
@@ -352,34 +353,34 @@
352 self.expand_group(open_group.id)353 self.expand_group(open_group.id)
353 # Sort the images by its filename considering language specific.354 # Sort the images by its filename considering language specific.
354 # characters.355 # characters.
355 images.sort(key=lambda image_object: get_locale_key(os.path.split(str(image_object.filename))[1]))356 images.sort(key=lambda image_object: get_locale_key(image_object.file_path.name))
356 for image_file in images:357 for image in images:
357 log.debug('Loading image: {name}'.format(name=image_file.filename))358 log.debug('Loading image: {name}'.format(name=image.file_path))
358 filename = os.path.split(image_file.filename)[1]359 file_name = image.file_path.name
359 thumb = self.generate_thumbnail_path(image_file)360 thumbnail_path = self.generate_thumbnail_path(image)
360 if not os.path.exists(image_file.filename):361 if not image.file_path.exists():
361 icon = build_icon(':/general/general_delete.png')362 icon = build_icon(':/general/general_delete.png')
362 else:363 else:
363 if validate_thumb(Path(image_file.filename), Path(thumb)):364 if validate_thumb(image.file_path, thumbnail_path):
364 icon = build_icon(thumb)365 icon = build_icon(thumbnail_path)
365 else:366 else:
366 icon = create_thumb(image_file.filename, thumb)367 icon = create_thumb(image.file_path, thumbnail_path)
367 item_name = QtWidgets.QTreeWidgetItem([filename])368 item_name = QtWidgets.QTreeWidgetItem([file_name])
368 item_name.setText(0, filename)369 item_name.setText(0, file_name)
369 item_name.setIcon(0, icon)370 item_name.setIcon(0, icon)
370 item_name.setToolTip(0, image_file.filename)371 item_name.setToolTip(0, str(image.file_path))
371 item_name.setData(0, QtCore.Qt.UserRole, image_file)372 item_name.setData(0, QtCore.Qt.UserRole, image)
372 if image_file.group_id == 0:373 if image.group_id == 0:
373 self.list_view.addTopLevelItem(item_name)374 self.list_view.addTopLevelItem(item_name)
374 else:375 else:
375 group_items[image_file.group_id].addChild(item_name)376 group_items[image.group_id].addChild(item_name)
376 if not initial_load:377 if not initial_load:
377 self.main_window.increment_progress_bar()378 self.main_window.increment_progress_bar()
378 if not initial_load:379 if not initial_load:
379 self.main_window.finished_progress_bar()380 self.main_window.finished_progress_bar()
380 self.application.set_normal_cursor()381 self.application.set_normal_cursor()
381382
382 def validate_and_load(self, files, target_group=None):383 def validate_and_load(self, file_paths, target_group=None):
383 """384 """
384 Process a list for files either from the File Dialog or from Drag and Drop.385 Process a list for files either from the File Dialog or from Drag and Drop.
385 This method is overloaded from MediaManagerItem.386 This method is overloaded from MediaManagerItem.
@@ -388,15 +389,15 @@
388 :param target_group: The QTreeWidgetItem of the group that will be the parent of the added files389 :param target_group: The QTreeWidgetItem of the group that will be the parent of the added files
389 """390 """
390 self.application.set_normal_cursor()391 self.application.set_normal_cursor()
391 self.load_list(files, target_group)392 self.load_list(file_paths, target_group)
392 last_dir = os.path.split(files[0])[0]393 last_dir = file_paths[0].parent
393 Settings().setValue(self.settings_section + '/last directory', Path(last_dir))394 Settings().setValue(self.settings_section + '/last directory', last_dir)
394395
395 def load_list(self, images, target_group=None, initial_load=False):396 def load_list(self, image_paths, target_group=None, initial_load=False):
396 """397 """
397 Add new images to the database. This method is called when adding images using the Add button or DnD.398 Add new images to the database. This method is called when adding images using the Add button or DnD.
398399
399 :param images: A List of strings containing the filenames of the files to be loaded400 :param list[openlp.core.common.Path] image_paths: A list of file paths to the images to be loaded
400 :param target_group: The QTreeWidgetItem of the group that will be the parent of the added files401 :param target_group: The QTreeWidgetItem of the group that will be the parent of the added files
401 :param initial_load: When set to False, the busy cursor and progressbar will be shown while loading images402 :param initial_load: When set to False, the busy cursor and progressbar will be shown while loading images
402 """403 """
@@ -429,7 +430,7 @@
429 else:430 else:
430 self.choose_group_form.existing_radio_button.setDisabled(False)431 self.choose_group_form.existing_radio_button.setDisabled(False)
431 self.choose_group_form.group_combobox.setDisabled(False)432 self.choose_group_form.group_combobox.setDisabled(False)
432 # Ask which group the images should be saved in433 # Ask which group the image_paths should be saved in
433 if self.choose_group_form.exec(selected_group=preselect_group):434 if self.choose_group_form.exec(selected_group=preselect_group):
434 if self.choose_group_form.nogroup_radio_button.isChecked():435 if self.choose_group_form.nogroup_radio_button.isChecked():
435 # User chose 'No group'436 # User chose 'No group'
@@ -461,33 +462,33 @@
461 return462 return
462 # Initialize busy cursor and progress bar463 # Initialize busy cursor and progress bar
463 self.application.set_busy_cursor()464 self.application.set_busy_cursor()
464 self.main_window.display_progress_bar(len(images))465 self.main_window.display_progress_bar(len(image_paths))
465 # Save the new images in the database466 # Save the new image_paths in the database
466 self.save_new_images_list(images, group_id=parent_group.id, reload_list=False)467 self.save_new_images_list(image_paths, group_id=parent_group.id, reload_list=False)
467 self.load_full_list(self.manager.get_all_objects(ImageFilenames, order_by_ref=ImageFilenames.filename),468 self.load_full_list(self.manager.get_all_objects(ImageFilenames, order_by_ref=ImageFilenames.file_path),
468 initial_load=initial_load, open_group=parent_group)469 initial_load=initial_load, open_group=parent_group)
469 self.application.set_normal_cursor()470 self.application.set_normal_cursor()
470471
471 def save_new_images_list(self, images_list, group_id=0, reload_list=True):472 def save_new_images_list(self, image_paths, group_id=0, reload_list=True):
472 """473 """
473 Convert a list of image filenames to ImageFilenames objects and save them in the database.474 Convert a list of image filenames to ImageFilenames objects and save them in the database.
474475
475 :param images_list: A List of strings containing image filenames476 :param list[Path] image_paths: A List of file paths to image
476 :param group_id: The ID of the group to save the images in477 :param group_id: The ID of the group to save the images in
477 :param reload_list: This boolean is set to True when the list in the interface should be reloaded after saving478 :param reload_list: This boolean is set to True when the list in the interface should be reloaded after saving
478 the new images479 the new images
479 """480 """
480 for filename in images_list:481 for image_path in image_paths:
481 if not isinstance(filename, str):482 if not isinstance(image_path, Path):
482 continue483 continue
483 log.debug('Adding new image: {name}'.format(name=filename))484 log.debug('Adding new image: {name}'.format(name=image_path))
484 image_file = ImageFilenames()485 image_file = ImageFilenames()
485 image_file.group_id = group_id486 image_file.group_id = group_id
486 image_file.filename = str(filename)487 image_file.file_path = image_path
487 self.manager.save_object(image_file)488 self.manager.save_object(image_file)
488 self.main_window.increment_progress_bar()489 self.main_window.increment_progress_bar()
489 if reload_list and images_list:490 if reload_list and image_paths:
490 self.load_full_list(self.manager.get_all_objects(ImageFilenames, order_by_ref=ImageFilenames.filename))491 self.load_full_list(self.manager.get_all_objects(ImageFilenames, order_by_ref=ImageFilenames.file_path))
491492
492 def dnd_move_internal(self, target):493 def dnd_move_internal(self, target):
493 """494 """
@@ -581,8 +582,8 @@
581 return False582 return False
582 # Find missing files583 # Find missing files
583 for image in images:584 for image in images:
584 if not os.path.exists(image.filename):585 if not image.file_path.exists():
585 missing_items_file_names.append(image.filename)586 missing_items_file_names.append(str(image.file_path))
586 # We cannot continue, as all images do not exist.587 # We cannot continue, as all images do not exist.
587 if not images:588 if not images:
588 if not remote:589 if not remote:
@@ -601,9 +602,9 @@
601 return False602 return False
602 # Continue with the existing images.603 # Continue with the existing images.
603 for image in images:604 for image in images:
604 name = os.path.split(image.filename)[1]605 name = image.file_path.name
605 thumbnail = self.generate_thumbnail_path(image)606 thumbnail_path = self.generate_thumbnail_path(image)
606 service_item.add_from_image(image.filename, name, background, thumbnail)607 service_item.add_from_image(str(image.file_path), name, background, str(thumbnail_path))
607 return True608 return True
608609
609 def check_group_exists(self, new_group):610 def check_group_exists(self, new_group):
@@ -640,7 +641,7 @@
640 if not self.check_group_exists(new_group):641 if not self.check_group_exists(new_group):
641 if self.manager.save_object(new_group):642 if self.manager.save_object(new_group):
642 self.load_full_list(self.manager.get_all_objects(643 self.load_full_list(self.manager.get_all_objects(
643 ImageFilenames, order_by_ref=ImageFilenames.filename))644 ImageFilenames, order_by_ref=ImageFilenames.file_path))
644 self.expand_group(new_group.id)645 self.expand_group(new_group.id)
645 self.fill_groups_combobox(self.choose_group_form.group_combobox)646 self.fill_groups_combobox(self.choose_group_form.group_combobox)
646 self.fill_groups_combobox(self.add_group_form.parent_group_combobox)647 self.fill_groups_combobox(self.add_group_form.parent_group_combobox)
@@ -675,9 +676,9 @@
675 if not isinstance(bitem.data(0, QtCore.Qt.UserRole), ImageFilenames):676 if not isinstance(bitem.data(0, QtCore.Qt.UserRole), ImageFilenames):
676 # Only continue when an image is selected.677 # Only continue when an image is selected.
677 return678 return
678 filename = bitem.data(0, QtCore.Qt.UserRole).filename679 file_path = bitem.data(0, QtCore.Qt.UserRole).file_path
679 if os.path.exists(filename):680 if file_path.exists():
680 if self.live_controller.display.direct_image(filename, background):681 if self.live_controller.display.direct_image(str(file_path), background):
681 self.reset_action.setVisible(True)682 self.reset_action.setVisible(True)
682 else:683 else:
683 critical_error_message_box(684 critical_error_message_box(
@@ -687,22 +688,22 @@
687 critical_error_message_box(688 critical_error_message_box(
688 UiStrings().LiveBGError,689 UiStrings().LiveBGError,
689 translate('ImagePlugin.MediaItem', 'There was a problem replacing your background, '690 translate('ImagePlugin.MediaItem', 'There was a problem replacing your background, '
690 'the image file "{name}" no longer exists.').format(name=filename))691 'the image file "{name}" no longer exists.').format(name=file_path))
691692
692 def search(self, string, show_error=True):693 def search(self, string, show_error=True):
693 """694 """
694 Perform a search on the image file names.695 Perform a search on the image file names.
695696
696 :param string: The glob to search for697 :param str string: The glob to search for
697 :param show_error: Unused.698 :param bool show_error: Unused.
698 """699 """
699 files = self.manager.get_all_objects(700 files = self.manager.get_all_objects(
700 ImageFilenames, filter_clause=ImageFilenames.filename.contains(string),701 ImageFilenames, filter_clause=ImageFilenames.file_path.contains(string),
701 order_by_ref=ImageFilenames.filename)702 order_by_ref=ImageFilenames.file_path)
702 results = []703 results = []
703 for file_object in files:704 for file_object in files:
704 filename = os.path.split(str(file_object.filename))[1]705 file_name = file_object.file_path.name
705 results.append([file_object.filename, filename])706 results.append([str(file_object.file_path), file_name])
706 return results707 return results
707708
708 def create_item_from_id(self, item_id):709 def create_item_from_id(self, item_id):
@@ -711,8 +712,9 @@
711712
712 :param item_id: Id to make live713 :param item_id: Id to make live
713 """714 """
715 item_id = Path(item_id)
714 item = QtWidgets.QTreeWidgetItem()716 item = QtWidgets.QTreeWidgetItem()
715 item_data = self.manager.get_object_filtered(ImageFilenames, ImageFilenames.filename == item_id)717 item_data = self.manager.get_object_filtered(ImageFilenames, ImageFilenames.file_path == item_id)
716 item.setText(0, os.path.basename(item_data.filename))718 item.setText(0, item_data.file_path.name)
717 item.setData(0, QtCore.Qt.UserRole, item_data)719 item.setData(0, QtCore.Qt.UserRole, item_data)
718 return item720 return item
719721
=== added file 'openlp/plugins/images/lib/upgrade.py'
--- openlp/plugins/images/lib/upgrade.py 1970-01-01 00:00:00 +0000
+++ openlp/plugins/images/lib/upgrade.py 2017-09-25 17:10:35 +0000
@@ -0,0 +1,70 @@
1# -*- coding: utf-8 -*-
2# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
3
4###############################################################################
5# OpenLP - Open Source Lyrics Projection #
6# --------------------------------------------------------------------------- #
7# Copyright (c) 2008-2017 OpenLP Developers #
8# --------------------------------------------------------------------------- #
9# This program is free software; you can redistribute it and/or modify it #
10# under the terms of the GNU General Public License as published by the Free #
11# Software Foundation; version 2 of the License. #
12# #
13# This program is distributed in the hope that it will be useful, but WITHOUT #
14# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
15# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
16# more details. #
17# #
18# You should have received a copy of the GNU General Public License along #
19# with this program; if not, write to the Free Software Foundation, Inc., 59 #
20# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
21###############################################################################
22"""
23The :mod:`upgrade` module provides the migration path for the OLP Paths database
24"""
25import json
26import logging
27
28from sqlalchemy import Column, Table
29
30from openlp.core.common import AppLocation
31from openlp.core.common.db import drop_columns
32from openlp.core.common.json import OpenLPJsonEncoder
33from openlp.core.common.path import Path
34from openlp.core.lib.db import PathType, get_upgrade_op
35
36log = logging.getLogger(__name__)
37__version__ = 2
38
39
40def upgrade_1(session, metadata):
41 """
42 Version 1 upgrade - old db might/might not be versioned.
43 """
44 log.debug('Skipping upgrade_1 of files DB - not used')
45
46
47def upgrade_2(session, metadata):
48 """
49 Version 2 upgrade - Move file path from old db to JSON encoded path to new db. Added during 2.5 dev
50 """
51 # TODO: Update tests
52 log.debug('Starting upgrade_2 for file_path to JSON')
53 old_table = Table('image_filenames', metadata, autoload=True)
54 if 'file_path' not in [col.name for col in old_table.c.values()]:
55 op = get_upgrade_op(session)
56 op.add_column('image_filenames', Column('file_path', PathType()))
57 conn = op.get_bind()
58 results = conn.execute('SELECT * FROM image_filenames')
59 data_path = AppLocation.get_data_path()
60 for row in results.fetchall():
61 file_path_json = json.dumps(Path(row.filename), cls=OpenLPJsonEncoder, base_path=data_path)
62 sql = 'UPDATE image_filenames SET file_path = \'{file_path_json}\' WHERE id = {id}'.format(
63 file_path_json=file_path_json, id=row.id)
64 conn.execute(sql)
65 # Drop old columns
66 if metadata.bind.url.get_dialect().name == 'sqlite':
67 drop_columns(op, 'image_filenames', ['filename', ])
68 else:
69 op.drop_constraint('image_filenames', 'foreignkey')
70 op.drop_column('image_filenames', 'filenames')
071
=== modified file 'openlp/plugins/songusage/forms/songusagedetaildialog.py'
--- openlp/plugins/songusage/forms/songusagedetaildialog.py 2017-06-09 06:06:49 +0000
+++ openlp/plugins/songusage/forms/songusagedetaildialog.py 2017-09-25 17:10:35 +0000
@@ -19,7 +19,6 @@
19# with this program; if not, write to the Free Software Foundation, Inc., 59 #19# with this program; if not, write to the Free Software Foundation, Inc., 59 #
20# Temple Place, Suite 330, Boston, MA 02111-1307 USA #20# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
21###############################################################################21###############################################################################
22
23from PyQt5 import QtCore, QtWidgets22from PyQt5 import QtCore, QtWidgets
2423
25from openlp.core.common import translate24from openlp.core.common import translate
2625
=== modified file 'openlp/plugins/songusage/forms/songusagedetailform.py'
--- openlp/plugins/songusage/forms/songusagedetailform.py 2017-08-26 15:06:11 +0000
+++ openlp/plugins/songusage/forms/songusagedetailform.py 2017-09-25 17:10:35 +0000
@@ -19,7 +19,6 @@
19# with this program; if not, write to the Free Software Foundation, Inc., 59 #19# with this program; if not, write to the Free Software Foundation, Inc., 59 #
20# Temple Place, Suite 330, Boston, MA 02111-1307 USA #20# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
21###############################################################################21###############################################################################
22
23import logging22import logging
24import os23import os
2524
@@ -60,7 +59,7 @@
6059
61 def on_report_path_edit_path_changed(self, file_path):60 def on_report_path_edit_path_changed(self, file_path):
62 """61 """
63 Called when the path in the `PathEdit` has changed62 Handle the `pathEditChanged` signal from report_path_edit
6463
65 :param openlp.core.common.path.Path file_path: The new path.64 :param openlp.core.common.path.Path file_path: The new path.
66 :rtype: None65 :rtype: None
@@ -72,7 +71,7 @@
72 Ok was triggered so lets save the data and run the report71 Ok was triggered so lets save the data and run the report
73 """72 """
74 log.debug('accept')73 log.debug('accept')
75 path = path_to_str(self.report_path_edit.path)74 path = self.report_path_edit.path
76 if not path:75 if not path:
77 self.main_window.error_message(76 self.main_window.error_message(
78 translate('SongUsagePlugin.SongUsageDetailForm', 'Output Path Not Selected'),77 translate('SongUsagePlugin.SongUsageDetailForm', 'Output Path Not Selected'),
@@ -80,7 +79,7 @@
80 ' song usage report. \nPlease select an existing path on your computer.')79 ' song usage report. \nPlease select an existing path on your computer.')
81 )80 )
82 return81 return
83 check_directory_exists(Path(path))82 check_directory_exists(path)
84 file_name = translate('SongUsagePlugin.SongUsageDetailForm',83 file_name = translate('SongUsagePlugin.SongUsageDetailForm',
85 'usage_detail_{old}_{new}.txt'84 'usage_detail_{old}_{new}.txt'
86 ).format(old=self.from_date_calendar.selectedDate().toString('ddMMyyyy'),85 ).format(old=self.from_date_calendar.selectedDate().toString('ddMMyyyy'),
@@ -91,29 +90,25 @@
91 SongUsageItem, and_(SongUsageItem.usagedate >= self.from_date_calendar.selectedDate().toPyDate(),90 SongUsageItem, and_(SongUsageItem.usagedate >= self.from_date_calendar.selectedDate().toPyDate(),
92 SongUsageItem.usagedate < self.to_date_calendar.selectedDate().toPyDate()),91 SongUsageItem.usagedate < self.to_date_calendar.selectedDate().toPyDate()),
93 [SongUsageItem.usagedate, SongUsageItem.usagetime])92 [SongUsageItem.usagedate, SongUsageItem.usagetime])
94 report_file_name = os.path.join(path, file_name)93 report_file_name = path / file_name
95 file_handle = None
96 try:94 try:
97 file_handle = open(report_file_name, 'wb')95 with report_file_name.open('wb') as file_handle:
98 for instance in usage:96 for instance in usage:
99 record = ('\"{date}\",\"{time}\",\"{title}\",\"{copyright}\",\"{ccli}\",\"{authors}\",'97 record = ('\"{date}\",\"{time}\",\"{title}\",\"{copyright}\",\"{ccli}\",\"{authors}\",'
100 '\"{name}\",\"{source}\"\n').format(date=instance.usagedate, time=instance.usagetime,98 '\"{name}\",\"{source}\"\n').format(date=instance.usagedate, time=instance.usagetime,
101 title=instance.title, copyright=instance.copyright,99 title=instance.title, copyright=instance.copyright,
102 ccli=instance.ccl_number, authors=instance.authors,100 ccli=instance.ccl_number, authors=instance.authors,
103 name=instance.plugin_name, source=instance.source)101 name=instance.plugin_name, source=instance.source)
104 file_handle.write(record.encode('utf-8'))102 file_handle.write(record.encode('utf-8'))
105 self.main_window.information_message(103 self.main_window.information_message(
106 translate('SongUsagePlugin.SongUsageDetailForm', 'Report Creation'),104 translate('SongUsagePlugin.SongUsageDetailForm', 'Report Creation'),
107 translate('SongUsagePlugin.SongUsageDetailForm',105 translate('SongUsagePlugin.SongUsageDetailForm',
108 'Report \n{name} \nhas been successfully created. ').format(name=report_file_name)106 'Report \n{name} \nhas been successfully created. ').format(name=report_file_name)
109 )107 )
110 except OSError as ose:108 except OSError as ose:
111 log.exception('Failed to write out song usage records')109 log.exception('Failed to write out song usage records')
112 critical_error_message_box(translate('SongUsagePlugin.SongUsageDetailForm', 'Report Creation Failed'),110 critical_error_message_box(translate('SongUsagePlugin.SongUsageDetailForm', 'Report Creation Failed'),
113 translate('SongUsagePlugin.SongUsageDetailForm',111 translate('SongUsagePlugin.SongUsageDetailForm',
114 'An error occurred while creating the report: {error}'112 'An error occurred while creating the report: {error}'
115 ).format(error=ose.strerror))113 ).format(error=ose.strerror))
116 finally:
117 if file_handle:
118 file_handle.close()
119 self.close()114 self.close()
120115
=== modified file 'tests/functional/openlp_core_ui/test_exceptionform.py'
--- tests/functional/openlp_core_ui/test_exceptionform.py 2017-09-07 21:52:39 +0000
+++ tests/functional/openlp_core_ui/test_exceptionform.py 2017-09-25 17:10:35 +0000
@@ -22,11 +22,11 @@
22"""22"""
23Package to test the openlp.core.ui.exeptionform package.23Package to test the openlp.core.ui.exeptionform package.
24"""24"""
25
26import os25import os
27import tempfile26import tempfile
27
28from unittest import TestCase28from unittest import TestCase
29from unittest.mock import mock_open, patch29from unittest.mock import call, patch
3030
31from openlp.core.common import Registry31from openlp.core.common import Registry
32from openlp.core.common.path import Path32from openlp.core.common.path import Path
@@ -142,15 +142,15 @@
142 test_form = exceptionform.ExceptionForm()142 test_form = exceptionform.ExceptionForm()
143 test_form.file_attachment = None143 test_form.file_attachment = None
144144
145 with patch.object(test_form, '_pyuno_import') as mock_pyuno:145 with patch.object(test_form, '_pyuno_import') as mock_pyuno, \
146 with patch.object(test_form.exception_text_edit, 'toPlainText') as mock_traceback:146 patch.object(test_form.exception_text_edit, 'toPlainText') as mock_traceback, \
147 with patch.object(test_form.description_text_edit, 'toPlainText') as mock_description:147 patch.object(test_form.description_text_edit, 'toPlainText') as mock_description:
148 mock_pyuno.return_value = 'UNO Bridge Test'148 mock_pyuno.return_value = 'UNO Bridge Test'
149 mock_traceback.return_value = 'openlp: Traceback Test'149 mock_traceback.return_value = 'openlp: Traceback Test'
150 mock_description.return_value = 'Description Test'150 mock_description.return_value = 'Description Test'
151151
152 # WHEN: on_save_report_button_clicked called152 # WHEN: on_save_report_button_clicked called
153 test_form.on_send_report_button_clicked()153 test_form.on_send_report_button_clicked()
154154
155 # THEN: Verify strings were formatted properly155 # THEN: Verify strings were formatted properly
156 mocked_add_query_item.assert_called_with('body', MAIL_ITEM_TEXT)156 mocked_add_query_item.assert_called_with('body', MAIL_ITEM_TEXT)
@@ -182,25 +182,24 @@
182 mocked_qt.PYQT_VERSION_STR = 'PyQt5 Test'182 mocked_qt.PYQT_VERSION_STR = 'PyQt5 Test'
183 mocked_is_linux.return_value = False183 mocked_is_linux.return_value = False
184 mocked_application_version.return_value = 'Trunk Test'184 mocked_application_version.return_value = 'Trunk Test'
185 mocked_save_filename.return_value = (Path('testfile.txt'), 'filter')185
186186 with patch.object(Path, 'open') as mocked_path_open:
187 test_form = exceptionform.ExceptionForm()187 test_path = Path('testfile.txt')
188 test_form.file_attachment = None188 mocked_save_filename.return_value = test_path, 'ext'
189189
190 with patch.object(test_form, '_pyuno_import') as mock_pyuno:190 test_form = exceptionform.ExceptionForm()
191 with patch.object(test_form.exception_text_edit, 'toPlainText') as mock_traceback:191 test_form.file_attachment = None
192 with patch.object(test_form.description_text_edit, 'toPlainText') as mock_description:192
193 with patch("openlp.core.ui.exceptionform.open", mock_open(), create=True) as mocked_open:193 with patch.object(test_form, '_pyuno_import') as mock_pyuno, \
194 mock_pyuno.return_value = 'UNO Bridge Test'194 patch.object(test_form.exception_text_edit, 'toPlainText') as mock_traceback, \
195 mock_traceback.return_value = 'openlp: Traceback Test'195 patch.object(test_form.description_text_edit, 'toPlainText') as mock_description:
196 mock_description.return_value = 'Description Test'196 mock_pyuno.return_value = 'UNO Bridge Test'
197197 mock_traceback.return_value = 'openlp: Traceback Test'
198 # WHEN: on_save_report_button_clicked called198 mock_description.return_value = 'Description Test'
199 test_form.on_save_report_button_clicked()199
200 # WHEN: on_save_report_button_clicked called
201 test_form.on_save_report_button_clicked()
200202
201 # THEN: Verify proper calls to save file203 # THEN: Verify proper calls to save file
202 # self.maxDiff = None204 # self.maxDiff = None
203 check_text = "call().write({text})".format(text=MAIL_ITEM_TEXT.__repr__())205 mocked_path_open.assert_has_calls([call().__enter__().write(MAIL_ITEM_TEXT)])
204 write_text = "{text}".format(text=mocked_open.mock_calls[1])
205 mocked_open.assert_called_with('testfile.txt', 'w')
206 self.assertEquals(check_text, write_text, "Saved information should match test text")
207206
=== modified file 'tests/functional/openlp_plugins/images/test_lib.py'
--- tests/functional/openlp_plugins/images/test_lib.py 2017-08-26 15:06:11 +0000
+++ tests/functional/openlp_plugins/images/test_lib.py 2017-09-25 17:10:35 +0000
@@ -58,7 +58,7 @@
58 Test that the validate_and_load_test() method when called without a group58 Test that the validate_and_load_test() method when called without a group
59 """59 """
60 # GIVEN: A list of files60 # GIVEN: A list of files
61 file_list = ['/path1/image1.jpg', '/path2/image2.jpg']61 file_list = [Path('path1', 'image1.jpg'), Path('path2', 'image2.jpg')]
6262
63 # WHEN: Calling validate_and_load with the list of files63 # WHEN: Calling validate_and_load with the list of files
64 self.media_item.validate_and_load(file_list)64 self.media_item.validate_and_load(file_list)
@@ -66,7 +66,7 @@
66 # THEN: load_list should have been called with the file list and None,66 # THEN: load_list should have been called with the file list and None,
67 # the directory should have been saved to the settings67 # the directory should have been saved to the settings
68 mocked_load_list.assert_called_once_with(file_list, None)68 mocked_load_list.assert_called_once_with(file_list, None)
69 mocked_settings().setValue.assert_called_once_with(ANY, Path('/', 'path1'))69 mocked_settings().setValue.assert_called_once_with(ANY, Path('path1'))
7070
71 @patch('openlp.plugins.images.lib.mediaitem.ImageMediaItem.load_list')71 @patch('openlp.plugins.images.lib.mediaitem.ImageMediaItem.load_list')
72 @patch('openlp.plugins.images.lib.mediaitem.Settings')72 @patch('openlp.plugins.images.lib.mediaitem.Settings')
@@ -75,7 +75,7 @@
75 Test that the validate_and_load_test() method when called with a group75 Test that the validate_and_load_test() method when called with a group
76 """76 """
77 # GIVEN: A list of files77 # GIVEN: A list of files
78 file_list = ['/path1/image1.jpg', '/path2/image2.jpg']78 file_list = [Path('path1', 'image1.jpg'), Path('path2', 'image2.jpg')]
7979
80 # WHEN: Calling validate_and_load with the list of files and a group80 # WHEN: Calling validate_and_load with the list of files and a group
81 self.media_item.validate_and_load(file_list, 'group')81 self.media_item.validate_and_load(file_list, 'group')
@@ -83,7 +83,7 @@
83 # THEN: load_list should have been called with the file list and the group name,83 # THEN: load_list should have been called with the file list and the group name,
84 # the directory should have been saved to the settings84 # the directory should have been saved to the settings
85 mocked_load_list.assert_called_once_with(file_list, 'group')85 mocked_load_list.assert_called_once_with(file_list, 'group')
86 mocked_settings().setValue.assert_called_once_with(ANY, Path('/', 'path1'))86 mocked_settings().setValue.assert_called_once_with(ANY, Path('path1'))
8787
88 @patch('openlp.plugins.images.lib.mediaitem.ImageMediaItem.load_full_list')88 @patch('openlp.plugins.images.lib.mediaitem.ImageMediaItem.load_full_list')
89 def test_save_new_images_list_empty_list(self, mocked_load_full_list):89 def test_save_new_images_list_empty_list(self, mocked_load_full_list):
@@ -107,8 +107,8 @@
107 Test that the save_new_images_list() calls load_full_list() when reload_list is set to True107 Test that the save_new_images_list() calls load_full_list() when reload_list is set to True
108 """108 """
109 # GIVEN: A list with 1 image and a mocked out manager109 # GIVEN: A list with 1 image and a mocked out manager
110 image_list = ['test_image.jpg']110 image_list = [Path('test_image.jpg')]
111 ImageFilenames.filename = ''111 ImageFilenames.file_path = None
112 self.media_item.manager = MagicMock()112 self.media_item.manager = MagicMock()
113113
114 # WHEN: We run save_new_images_list with reload_list=True114 # WHEN: We run save_new_images_list with reload_list=True
@@ -118,7 +118,7 @@
118 self.assertEquals(mocked_load_full_list.call_count, 1, 'load_full_list() should have been called')118 self.assertEquals(mocked_load_full_list.call_count, 1, 'load_full_list() should have been called')
119119
120 # CLEANUP: Remove added attribute from ImageFilenames120 # CLEANUP: Remove added attribute from ImageFilenames
121 delattr(ImageFilenames, 'filename')121 delattr(ImageFilenames, 'file_path')
122122
123 @patch('openlp.plugins.images.lib.mediaitem.ImageMediaItem.load_full_list')123 @patch('openlp.plugins.images.lib.mediaitem.ImageMediaItem.load_full_list')
124 def test_save_new_images_list_single_image_without_reload(self, mocked_load_full_list):124 def test_save_new_images_list_single_image_without_reload(self, mocked_load_full_list):
@@ -126,7 +126,7 @@
126 Test that the save_new_images_list() doesn't call load_full_list() when reload_list is set to False126 Test that the save_new_images_list() doesn't call load_full_list() when reload_list is set to False
127 """127 """
128 # GIVEN: A list with 1 image and a mocked out manager128 # GIVEN: A list with 1 image and a mocked out manager
129 image_list = ['test_image.jpg']129 image_list = [Path('test_image.jpg')]
130 self.media_item.manager = MagicMock()130 self.media_item.manager = MagicMock()
131131
132 # WHEN: We run save_new_images_list with reload_list=False132 # WHEN: We run save_new_images_list with reload_list=False
@@ -141,7 +141,7 @@
141 Test that the save_new_images_list() saves all images in the list141 Test that the save_new_images_list() saves all images in the list
142 """142 """
143 # GIVEN: A list with 3 images143 # GIVEN: A list with 3 images
144 image_list = ['test_image_1.jpg', 'test_image_2.jpg', 'test_image_3.jpg']144 image_list = [Path('test_image_1.jpg'), Path('test_image_2.jpg'), Path('test_image_3.jpg')]
145 self.media_item.manager = MagicMock()145 self.media_item.manager = MagicMock()
146146
147 # WHEN: We run save_new_images_list with the list of 3 images147 # WHEN: We run save_new_images_list with the list of 3 images
@@ -157,7 +157,7 @@
157 Test that the save_new_images_list() ignores everything in the provided list except strings157 Test that the save_new_images_list() ignores everything in the provided list except strings
158 """158 """
159 # GIVEN: A list with images and objects159 # GIVEN: A list with images and objects
160 image_list = ['test_image_1.jpg', None, True, ImageFilenames(), 'test_image_2.jpg']160 image_list = [Path('test_image_1.jpg'), None, True, ImageFilenames(), Path('test_image_2.jpg')]
161 self.media_item.manager = MagicMock()161 self.media_item.manager = MagicMock()
162162
163 # WHEN: We run save_new_images_list with the list of images and objects163 # WHEN: We run save_new_images_list with the list of images and objects
@@ -191,7 +191,7 @@
191 ImageGroups.parent_id = 1191 ImageGroups.parent_id = 1
192 self.media_item.manager = MagicMock()192 self.media_item.manager = MagicMock()
193 self.media_item.manager.get_all_objects.side_effect = self._recursively_delete_group_side_effect193 self.media_item.manager.get_all_objects.side_effect = self._recursively_delete_group_side_effect
194 self.media_item.service_path = ''194 self.media_item.service_path = Path()
195 test_group = ImageGroups()195 test_group = ImageGroups()
196 test_group.id = 1196 test_group.id = 1
197197
@@ -215,13 +215,13 @@
215 # Create some fake objects that should be removed215 # Create some fake objects that should be removed
216 returned_object1 = ImageFilenames()216 returned_object1 = ImageFilenames()
217 returned_object1.id = 1217 returned_object1.id = 1
218 returned_object1.filename = '/tmp/test_file_1.jpg'218 returned_object1.file_path = Path('/', 'tmp', 'test_file_1.jpg')
219 returned_object2 = ImageFilenames()219 returned_object2 = ImageFilenames()
220 returned_object2.id = 2220 returned_object2.id = 2
221 returned_object2.filename = '/tmp/test_file_2.jpg'221 returned_object2.file_path = Path('/', 'tmp', 'test_file_2.jpg')
222 returned_object3 = ImageFilenames()222 returned_object3 = ImageFilenames()
223 returned_object3.id = 3223 returned_object3.id = 3
224 returned_object3.filename = '/tmp/test_file_3.jpg'224 returned_object3.file_path = Path('/', 'tmp', 'test_file_3.jpg')
225 return [returned_object1, returned_object2, returned_object3]225 return [returned_object1, returned_object2, returned_object3]
226 if args[1] == ImageGroups and args[2]:226 if args[1] == ImageGroups and args[2]:
227 # Change the parent_id that is matched so we don't get into an endless loop227 # Change the parent_id that is matched so we don't get into an endless loop
@@ -243,9 +243,9 @@
243 test_image = ImageFilenames()243 test_image = ImageFilenames()
244 test_image.id = 1244 test_image.id = 1
245 test_image.group_id = 1245 test_image.group_id = 1
246 test_image.filename = 'imagefile.png'246 test_image.file_path = Path('imagefile.png')
247 self.media_item.manager = MagicMock()247 self.media_item.manager = MagicMock()
248 self.media_item.service_path = ''248 self.media_item.service_path = Path()
249 self.media_item.list_view = MagicMock()249 self.media_item.list_view = MagicMock()
250 mocked_row_item = MagicMock()250 mocked_row_item = MagicMock()
251 mocked_row_item.data.return_value = test_image251 mocked_row_item.data.return_value = test_image
@@ -265,13 +265,13 @@
265 # GIVEN: An ImageFilenames that already exists in the database265 # GIVEN: An ImageFilenames that already exists in the database
266 image_file = ImageFilenames()266 image_file = ImageFilenames()
267 image_file.id = 1267 image_file.id = 1
268 image_file.filename = '/tmp/test_file_1.jpg'268 image_file.file_path = Path('/', 'tmp', 'test_file_1.jpg')
269 self.media_item.manager = MagicMock()269 self.media_item.manager = MagicMock()
270 self.media_item.manager.get_object_filtered.return_value = image_file270 self.media_item.manager.get_object_filtered.return_value = image_file
271 ImageFilenames.filename = ''271 ImageFilenames.file_path = None
272272
273 # WHEN: create_item_from_id() is called273 # WHEN: create_item_from_id() is called
274 item = self.media_item.create_item_from_id(1)274 item = self.media_item.create_item_from_id('1')
275275
276 # THEN: A QTreeWidgetItem should be created with the above model object as it's data276 # THEN: A QTreeWidgetItem should be created with the above model object as it's data
277 self.assertIsInstance(item, QtWidgets.QTreeWidgetItem)277 self.assertIsInstance(item, QtWidgets.QTreeWidgetItem)
@@ -279,4 +279,4 @@
279 item_data = item.data(0, QtCore.Qt.UserRole)279 item_data = item.data(0, QtCore.Qt.UserRole)
280 self.assertIsInstance(item_data, ImageFilenames)280 self.assertIsInstance(item_data, ImageFilenames)
281 self.assertEqual(1, item_data.id)281 self.assertEqual(1, item_data.id)
282 self.assertEqual('/tmp/test_file_1.jpg', item_data.filename)282 self.assertEqual(Path('/', 'tmp', 'test_file_1.jpg'), item_data.file_path)
283283
=== added file 'tests/functional/openlp_plugins/images/test_upgrade.py'
--- tests/functional/openlp_plugins/images/test_upgrade.py 1970-01-01 00:00:00 +0000
+++ tests/functional/openlp_plugins/images/test_upgrade.py 2017-09-25 17:10:35 +0000
@@ -0,0 +1,83 @@
1# -*- coding: utf-8 -*-
2# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
3
4###############################################################################
5# OpenLP - Open Source Lyrics Projection #
6# --------------------------------------------------------------------------- #
7# Copyright (c) 2008-2017 OpenLP Developers #
8# --------------------------------------------------------------------------- #
9# This program is free software; you can redistribute it and/or modify it #
10# under the terms of the GNU General Public License as published by the Free #
11# Software Foundation; version 2 of the License. #
12# #
13# This program is distributed in the hope that it will be useful, but WITHOUT #
14# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
15# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
16# more details. #
17# #
18# You should have received a copy of the GNU General Public License along #
19# with this program; if not, write to the Free Software Foundation, Inc., 59 #
20# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
21###############################################################################
22"""
23This module contains tests for the lib submodule of the Images plugin.
24"""
25import os
26import shutil
27from tempfile import mkdtemp
28from unittest import TestCase
29from unittest.mock import patch
30
31from openlp.core.common import AppLocation, Settings
32from openlp.core.common.path import Path
33from openlp.core.lib.db import Manager
34from openlp.plugins.images.lib import upgrade
35from openlp.plugins.images.lib.db import ImageFilenames, init_schema
36
37from tests.helpers.testmixin import TestMixin
38from tests.utils.constants import TEST_RESOURCES_PATH
39
40__default_settings__ = {
41 'images/db type': 'sqlite',
42 'images/background color': '#000000',
43}
44
45
46class TestImageDBUpgrade(TestCase, TestMixin):
47 """
48 Test that the image database is upgraded correctly
49 """
50 def setUp(self):
51 self.build_settings()
52 Settings().extend_default_settings(__default_settings__)
53 self.tmp_folder = mkdtemp()
54
55 def tearDown(self):
56 """
57 Delete all the C++ objects at the end so that we don't have a segfault
58 """
59 self.destroy_settings()
60 # Ignore errors since windows can have problems with locked files
61 shutil.rmtree(self.tmp_folder, ignore_errors=True)
62
63 def test_image_filenames_table(self):
64 """
65 Test that the ImageFilenames table is correctly upgraded to the latest version
66 """
67 # GIVEN: An unversioned image database
68 temp_db_name = os.path.join(self.tmp_folder, 'image-v0.sqlite')
69 shutil.copyfile(os.path.join(TEST_RESOURCES_PATH, 'images', 'image-v0.sqlite'), temp_db_name)
70
71 with patch.object(AppLocation, 'get_data_path', return_value=Path('/', 'test', 'dir')):
72 # WHEN: Initalising the database manager
73 manager = Manager('images', init_schema, db_file_path=temp_db_name, upgrade_mod=upgrade)
74
75 # THEN: The database should have been upgraded and image_filenames.file_path should return Path objects
76 upgraded_results = manager.get_all_objects(ImageFilenames)
77
78 expected_result_data = {1: Path('/', 'test', 'image1.jpg'),
79 2: Path('/', 'test', 'dir', 'image2.jpg'),
80 3: Path('/', 'test', 'dir', 'subdir', 'image3.jpg')}
81
82 for result in upgraded_results:
83 self.assertEqual(expected_result_data[result.id], result.file_path)
084
=== added directory 'tests/resources/images'
=== added file 'tests/resources/images/image-v0.sqlite'
1Binary 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 differ85Binary 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