Merge lp:~tomasgroth/openlp/presentation-load-speedup into lp:openlp
- presentation-load-speedup
- Merge into trunk
Status: | Needs review | ||||
---|---|---|---|---|---|
Proposed branch: | lp:~tomasgroth/openlp/presentation-load-speedup | ||||
Merge into: | lp:openlp | ||||
Diff against target: |
964 lines (+279/-81) 12 files modified
openlp/core/common/__init__.py (+15/-0) openlp/core/lib/serviceitem.py (+80/-19) openlp/core/ui/servicemanager.py (+77/-29) openlp/plugins/images/lib/db.py (+3/-1) openlp/plugins/images/lib/mediaitem.py (+3/-2) openlp/plugins/images/lib/upgrade.py (+22/-3) openlp/plugins/presentations/lib/mediaitem.py (+2/-1) openlp/plugins/presentations/lib/presentationcontroller.py (+29/-4) openlp/plugins/presentations/presentationplugin.py (+1/-1) tests/functional/openlp_core/lib/test_serviceitem.py (+24/-14) tests/functional/openlp_plugins/images/test_lib.py (+19/-5) tests/interfaces/openlp_core/widgets/test_views.py (+4/-2) |
||||
To merge this branch: | bzr merge lp:~tomasgroth/openlp/presentation-load-speedup | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Raoul Snyman | Needs Information | ||
Review via email: mp+367933@code.launchpad.net |
Commit message
WIP!
Fundamental change to how service files are storing data.
Description of the change
WIP!
Fundamental change to how service files are storing data.
When we add a file to a service file, instead of adding it with its name,
we should rename it so its name reflects the sha256 checksum of the
filecontent. So is a file is called "church-
as "505911856c191c
and no path/folders will be included. The new filename will also be added
to the service-item data. Doing this will avoid any problems with paths,
and if any files has the same hash it will be because they are the same, so
no duplicate content.
Avoiding folders from included files also allow us to use folders for
something else, namely thumbnails (for presentations). So reusing the the
example above, we could create a folder in the servicefile named
"505911856c191c
contains all the thumbnails (and slide notes and titles), which can then be
reused when loading the servicefile, thereby skipping the step of
recreating the thumbnails (and thereby fixing bug #1677037).
- 2829. By Tomas Groth
-
A few fixes
Phill (phill-ridout) wrote : | # |
Just a comment on implementation.
- 2830. By Tomas Groth
-
merge trunk
- 2831. By Tomas Groth
-
Added preliminary support for saving image thumbnails in servicefiles.
Raoul Snyman (raoul-snyman) wrote : | # |
Just a question about os.path vs Path
- 2832. By Tomas Groth
-
Make image plugin use hashes for id and thumbnails
- 2833. By Tomas Groth
-
merge trunk
- 2834. By Tomas Groth
-
merge trunk
- 2835. By Tomas Groth
-
More work on saving and loading images to servicefile with new file hashes.
- 2836. By Tomas Groth
-
minor stuff
- 2837. By Tomas Groth
-
merge trunk
- 2838. By Tomas Groth
-
Loading presentation thumbnails from servicefiles now works. Also introduced a new service file header attribute: stored_filename.
- 2839. By Tomas Groth
-
merge trunk
- 2840. By Tomas Groth
-
Change tests to match new implementation.
- 2841. By Tomas Groth
-
merge trunk
Unmerged revisions
- 2841. By Tomas Groth
-
merge trunk
- 2840. By Tomas Groth
-
Change tests to match new implementation.
- 2839. By Tomas Groth
-
merge trunk
- 2838. By Tomas Groth
-
Loading presentation thumbnails from servicefiles now works. Also introduced a new service file header attribute: stored_filename.
- 2837. By Tomas Groth
-
merge trunk
- 2836. By Tomas Groth
-
minor stuff
- 2835. By Tomas Groth
-
More work on saving and loading images to servicefile with new file hashes.
- 2834. By Tomas Groth
-
merge trunk
- 2833. By Tomas Groth
-
merge trunk
- 2832. By Tomas Groth
-
Make image plugin use hashes for id and thumbnails
Preview Diff
1 | === modified file 'openlp/core/common/__init__.py' |
2 | --- openlp/core/common/__init__.py 2019-07-27 06:37:16 +0000 |
3 | +++ openlp/core/common/__init__.py 2019-09-15 09:05:37 +0000 |
4 | @@ -282,6 +282,21 @@ |
5 | return hash_value |
6 | |
7 | |
8 | +def sha256_file_hash(filename): |
9 | + """ |
10 | + Returns the hashed output of sha256 on the file content using Python3 hashlib |
11 | + |
12 | + :param filename: Name of the file to hash |
13 | + :returns: str |
14 | + """ |
15 | + log.debug('sha256_hash(filename="{filename}")'.format(filename=filename)) |
16 | + hash_obj = hashlib.sha256() |
17 | + with open(filename, 'rb') as f: |
18 | + for chunk in iter(lambda: f.read(65536), b''): |
19 | + hash_obj.update(chunk) |
20 | + return hash_obj.hexdigest() |
21 | + |
22 | + |
23 | def qmd5_hash(salt=None, data=None): |
24 | """ |
25 | Returns the hashed output of MD5Sum on salt, data |
26 | |
27 | === modified file 'openlp/core/lib/serviceitem.py' |
28 | --- openlp/core/lib/serviceitem.py 2019-08-30 21:32:26 +0000 |
29 | +++ openlp/core/lib/serviceitem.py 2019-09-15 09:05:37 +0000 |
30 | @@ -30,11 +30,12 @@ |
31 | import uuid |
32 | from copy import deepcopy |
33 | from pathlib import Path |
34 | +from shutil import copytree |
35 | |
36 | from PyQt5 import QtGui |
37 | |
38 | from openlp.core.state import State |
39 | -from openlp.core.common import md5_hash |
40 | +from openlp.core.common import sha256_file_hash |
41 | from openlp.core.common.applocation import AppLocation |
42 | from openlp.core.common.i18n import translate |
43 | from openlp.core.common.mixins import RegistryProperties |
44 | @@ -113,6 +114,8 @@ |
45 | self.timed_slide_interval = 0 |
46 | self.will_auto_start = False |
47 | self.has_original_files = True |
48 | + self.sha256_file_hash = None |
49 | + self.stored_filename = None |
50 | self._new_item() |
51 | self.metadata = [] |
52 | |
53 | @@ -238,7 +241,7 @@ |
54 | self._print_slides.append(slide) |
55 | return self._print_slides |
56 | |
57 | - def add_from_image(self, path, title, background=None, thumbnail=None): |
58 | + def add_from_image(self, path, title, background=None, thumbnail=None, file_hash=None): |
59 | """ |
60 | Add an image slide to the service item. |
61 | |
62 | @@ -250,7 +253,9 @@ |
63 | if background: |
64 | self.image_border = background |
65 | self.service_item_type = ServiceItemType.Image |
66 | - slide = {'title': title, 'path': path} |
67 | + if not file_hash: |
68 | + file_hash = sha256_file_hash(path) |
69 | + slide = {'title': title, 'path': path, 'file_hash': file_hash} |
70 | if thumbnail: |
71 | slide['thumbnail'] = thumbnail |
72 | self.slides.append(slide) |
73 | @@ -274,7 +279,7 @@ |
74 | self.slides.append({'title': title, 'text': text, 'verse': verse_tag}) |
75 | self._new_item() |
76 | |
77 | - def add_from_command(self, path, file_name, image, display_title=None, notes=None): |
78 | + def add_from_command(self, path, file_name, image, display_title=None, notes=None, file_hash=None): |
79 | """ |
80 | Add a slide from a command. |
81 | |
82 | @@ -283,18 +288,23 @@ |
83 | :param image: The command of/for the slide. |
84 | :param display_title: Title to show in gui/webinterface, optional. |
85 | :param notes: Notes to show in the webinteface, optional. |
86 | + :param file_hash: Sha256 hash checksum of the file. |
87 | """ |
88 | self.service_item_type = ServiceItemType.Command |
89 | # If the item should have a display title but this frame doesn't have one, we make one up |
90 | if self.is_capable(ItemCapabilities.HasDisplayTitle) and not display_title: |
91 | display_title = translate('OpenLP.ServiceItem', |
92 | '[slide {frame:d}]').format(frame=len(self.slides) + 1) |
93 | + if file_hash: |
94 | + self.sha256_file_hash = file_hash |
95 | + else: |
96 | + file_location = Path(path) / file_name |
97 | + self.sha256_file_hash = sha256_file_hash(file_location) |
98 | + self.stored_filename = '{hash}{ext}'.format(hash=self.sha256_file_hash, ext=os.path.splitext(file_name)[1]) |
99 | # Update image path to match servicemanager location if file was loaded from service |
100 | if image and not self.has_original_files and self.name == 'presentations': |
101 | - file_location = os.path.join(path, file_name) |
102 | - file_location_hash = md5_hash(file_location.encode('utf-8')) |
103 | - image = os.path.join(AppLocation.get_section_data_path(self.name), 'thumbnails', file_location_hash, |
104 | - ntpath.basename(image)) # TODO: Pathlib |
105 | + image = os.path.join(str(AppLocation.get_section_data_path(self.name)), 'thumbnails', |
106 | + self.sha256_file_hash, ntpath.basename(image)) |
107 | self.slides.append({'title': file_name, 'image': image, 'path': path, 'display_title': display_title, |
108 | 'notes': notes, 'thumbnail': image}) |
109 | # if self.is_capable(ItemCapabilities.HasThumbnails): |
110 | @@ -305,6 +315,10 @@ |
111 | """ |
112 | This method returns some text which can be saved into the service file to represent this item. |
113 | """ |
114 | + if self.sha256_file_hash: |
115 | + stored_filename = '{hash}{ext}'.format(hash=self.sha256_file_hash, ext=os.path.splitext(self.title)[1]) |
116 | + else: |
117 | + stored_filename = None |
118 | service_header = { |
119 | 'name': self.name, |
120 | 'plugin': self.name, |
121 | @@ -329,7 +343,9 @@ |
122 | 'theme_overwritten': self.theme_overwritten, |
123 | 'will_auto_start': self.will_auto_start, |
124 | 'processor': self.processor, |
125 | - 'metadata': self.metadata |
126 | + 'metadata': self.metadata, |
127 | + 'sha256_file_hash': self.sha256_file_hash, |
128 | + 'stored_filename': stored_filename |
129 | } |
130 | service_data = [] |
131 | if self.service_item_type == ServiceItemType.Text: |
132 | @@ -341,12 +357,16 @@ |
133 | elif self.service_item_type == ServiceItemType.Image: |
134 | if lite_save: |
135 | for slide in self.slides: |
136 | - service_data.append({'title': slide['title'], 'path': slide['path']}) |
137 | + service_data.append({'title': slide['title'], 'path': slide['path'], |
138 | + 'file_hash': slide['file_hash']}) |
139 | else: |
140 | - service_data = [slide['title'] for slide in self.slides] |
141 | + for slide in self.slides: |
142 | + image_path = os.path.relpath(slide['thumbnail'], AppLocation().get_data_path()) |
143 | + service_data.append({'title': slide['title'], 'image': image_path, 'file_hash': slide['file_hash']}) |
144 | elif self.service_item_type == ServiceItemType.Command: |
145 | for slide in self.slides: |
146 | - service_data.append({'title': slide['title'], 'image': slide['image'], 'path': slide['path'], |
147 | + image_path = os.path.relpath(slide['image'], AppLocation().get_data_path()) |
148 | + service_data.append({'title': slide['title'], 'image': image_path, 'path': slide['path'], |
149 | 'display_title': slide['display_title'], 'notes': slide['notes']}) |
150 | return {'header': service_header, 'data': service_data} |
151 | |
152 | @@ -357,7 +377,7 @@ |
153 | self._display_slides = [] |
154 | self._rendered_slides = [] |
155 | |
156 | - def set_from_service(self, service_item, path=None): |
157 | + def set_from_service(self, service_item, path=None, version=2): |
158 | """ |
159 | This method takes a service item from a saved service file (passed from the ServiceManager) and extracts the |
160 | data actually required. |
161 | @@ -365,6 +385,7 @@ |
162 | :param service_item: The item to extract data from. |
163 | :param path: Defaults to *None*. This is the service manager path for things which have their files saved |
164 | with them or None when the saved service is lite and the original file paths need to be preserved. |
165 | + :param version: Format version of the data. |
166 | """ |
167 | log.debug('set_from_service called with path {path}'.format(path=path)) |
168 | header = service_item['serviceitem']['header'] |
169 | @@ -392,6 +413,8 @@ |
170 | self.processor = header.get('processor', None) |
171 | self.has_original_files = True |
172 | self.metadata = header.get('item_meta_data', []) |
173 | + self.sha256_file_hash = header.get('sha256_file_hash', None) |
174 | + self.stored_filename = header.get('stored_filename', self.title) |
175 | if 'background_audio' in header and State().check_preconditions('media'): |
176 | self.background_audio = [] |
177 | for file_path in header['background_audio']: |
178 | @@ -412,11 +435,24 @@ |
179 | if path: |
180 | self.has_original_files = False |
181 | for text_image in service_item['serviceitem']['data']: |
182 | - file_path = path / text_image |
183 | - self.add_from_image(file_path, text_image, background) |
184 | + file_hash = None |
185 | + if version >= 3: |
186 | + text = text_image['title'] |
187 | + file_hash = text_image['file_hash'] |
188 | + file_path = path / '{base}{ext}'.format(base=file_hash, ext=os.path.splitext(text)[1]) |
189 | + else: |
190 | + text = text_image |
191 | + file_path = path / text |
192 | + self.add_from_image(file_path, text, background, file_hash=file_hash) |
193 | else: |
194 | for text_image in service_item['serviceitem']['data']: |
195 | - self.add_from_image(text_image['path'], text_image['title'], background) |
196 | + file_hash = None |
197 | + if version >= 3: |
198 | + text = text_image['title'] |
199 | + file_hash = text_image['file_hash'] |
200 | + else: |
201 | + text = text_image |
202 | + self.add_from_image(text_image['path'], text, background, file_hash=file_hash) |
203 | elif self.service_item_type == ServiceItemType.Command: |
204 | for text_image in service_item['serviceitem']['data']: |
205 | if not self.title: |
206 | @@ -426,10 +462,22 @@ |
207 | self.add_from_command(text_image['path'], text_image['title'], text_image['image']) |
208 | elif path: |
209 | self.has_original_files = False |
210 | + # Copy any bundled thumbnails into the plugin thumbnail folder |
211 | + if version >= 3 and os.path.exists(path / self.sha256_file_hash) and \ |
212 | + os.path.isdir(path / self.sha256_file_hash): |
213 | + try: |
214 | + copytree(path / self.sha256_file_hash, |
215 | + AppLocation.get_section_data_path(self.name) / 'thumbnails' / |
216 | + self.sha256_file_hash) |
217 | + except FileExistsError: |
218 | + # Files already exists, just skip |
219 | + pass |
220 | self.add_from_command(path, text_image['title'], text_image['image'], |
221 | - text_image.get('display_title', ''), text_image.get('notes', '')) |
222 | + text_image.get('display_title', ''), text_image.get('notes', ''), |
223 | + file_hash=self.sha256_file_hash) |
224 | else: |
225 | - self.add_from_command(Path(text_image['path']), text_image['title'], text_image['image']) |
226 | + self.add_from_command(Path(text_image['path']), text_image['title'], text_image['image'], |
227 | + file_hash=self.sha256_file_hash) |
228 | self._new_item() |
229 | |
230 | def get_display_title(self): |
231 | @@ -565,6 +613,8 @@ |
232 | return '' |
233 | if self.is_image() or self.is_capable(ItemCapabilities.IsOptical): |
234 | path_from = frame['path'] |
235 | + elif self.is_command(): |
236 | + path_from = os.path.join(frame['path'], self.stored_filename) |
237 | else: |
238 | path_from = os.path.join(frame['path'], frame['title']) |
239 | if isinstance(path_from, str): |
240 | @@ -645,7 +695,7 @@ |
241 | self.is_valid = False |
242 | break |
243 | else: |
244 | - file_name = os.path.join(slide['path'], slide['title']) |
245 | + file_name = os.path.join(slide['path'], self.stored_filename) |
246 | if not os.path.exists(file_name): |
247 | self.is_valid = False |
248 | break |
249 | @@ -654,3 +704,14 @@ |
250 | if file_suffix.lower() not in suffixes: |
251 | self.is_valid = False |
252 | break |
253 | + |
254 | + def get_thumbnail_path(self): |
255 | + """ |
256 | + Returns the thumbnail folder. Should only be used for items that support thumbnails. |
257 | + """ |
258 | + if self.is_capable(ItemCapabilities.HasThumbnails): |
259 | + if self.is_command() and self.slides: |
260 | + return os.path.dirname(self.slides[0]['image']) |
261 | + elif self.is_image() and self.slides: |
262 | + return os.path.dirname(self.slides[0]['thumbnail']) |
263 | + return None |
264 | |
265 | === modified file 'openlp/core/ui/servicemanager.py' |
266 | --- openlp/core/ui/servicemanager.py 2019-08-30 11:28:10 +0000 |
267 | +++ openlp/core/ui/servicemanager.py 2019-09-15 09:05:37 +0000 |
268 | @@ -34,8 +34,8 @@ |
269 | |
270 | from PyQt5 import QtCore, QtGui, QtWidgets |
271 | |
272 | +from openlp.core.common import ThemeLevel, delete_file, sha256_file_hash |
273 | from openlp.core.state import State |
274 | -from openlp.core.common import ThemeLevel, delete_file |
275 | from openlp.core.common.actions import ActionList, CategoryOrder |
276 | from openlp.core.common.applocation import AppLocation |
277 | from openlp.core.common.i18n import UiStrings, format_time, translate |
278 | @@ -43,10 +43,10 @@ |
279 | from openlp.core.common.mixins import LogMixin, RegistryProperties |
280 | from openlp.core.common.registry import Registry, RegistryBase |
281 | from openlp.core.common.settings import Settings |
282 | -from openlp.core.lib import build_icon |
283 | +from openlp.core.lib import build_icon, ItemCapabilities |
284 | from openlp.core.lib.exceptions import ValidationError |
285 | from openlp.core.lib.plugin import PluginStatus |
286 | -from openlp.core.lib.serviceitem import ItemCapabilities, ServiceItem, ServiceItemType |
287 | +from openlp.core.lib.serviceitem import ServiceItem, ServiceItemType |
288 | from openlp.core.lib.ui import create_widget_action, critical_error_message_box, find_and_set_in_combo_box |
289 | from openlp.core.ui.icons import UiIcons |
290 | from openlp.core.ui.media import AUDIO_EXT, VIDEO_EXT |
291 | @@ -331,6 +331,7 @@ |
292 | self._service_path = None |
293 | self.service_has_all_original_files = True |
294 | self.list_double_clicked = False |
295 | + self.servicefile_version = None |
296 | |
297 | def bootstrap_initialise(self): |
298 | """ |
299 | @@ -516,9 +517,15 @@ |
300 | :return: service array |
301 | """ |
302 | service = [] |
303 | + # Regarding openlp-servicefile-version: |
304 | + # 1: OpenLP 1? Not used. |
305 | + # 2: OpenLP 2 (default when loading a service file without openlp-servicefile-version) |
306 | + # 3: The new format introduced in OpenLP 3.0. |
307 | + # Note that the servicefile-version numbering is not expected to follow the OpenLP version numbering. |
308 | core = { |
309 | 'lite-service': self._save_lite, |
310 | - 'service-theme': self.service_theme |
311 | + 'service-theme': self.service_theme, |
312 | + 'openlp-servicefile-version': 3 |
313 | } |
314 | service.append({'openlp_core': core}) |
315 | return service |
316 | @@ -536,16 +543,38 @@ |
317 | if item['service_item'].uses_file(): |
318 | for frame in item['service_item'].get_frames(): |
319 | path_from = item['service_item'].get_frame_path(frame=frame) |
320 | - if path_from in write_list or path_from in missing_list: |
321 | + path_from_path = Path(path_from) |
322 | + if item['service_item'].stored_filename: |
323 | + sha256_file_name = item['service_item'].stored_filename |
324 | + else: |
325 | + sha256_file_name = sha256_file_hash(path_from_path) + os.path.splitext(path_from)[1] |
326 | + path_from_tuple = (path_from_path, sha256_file_name) |
327 | + if path_from_tuple in write_list or path_from_path in missing_list: |
328 | continue |
329 | if not os.path.exists(path_from): |
330 | - missing_list.append(Path(path_from)) |
331 | + missing_list.append(path_from_path) |
332 | else: |
333 | - write_list.append(Path(path_from)) |
334 | + write_list.append(path_from_tuple) |
335 | + # For items that has thumbnails, add them to the list |
336 | + if item['service_item'].is_capable(ItemCapabilities.HasThumbnails): |
337 | + if item['service_item'].is_command(): |
338 | + thumbnail_path = item['service_item'].get_thumbnail_path() |
339 | + # Run through everything in the thumbnail folder and add pictures |
340 | + thumbnail_path_parent = Path(thumbnail_path).parent |
341 | + for filename in os.listdir(thumbnail_path): |
342 | + # Skip non-pictures |
343 | + if os.path.splitext(filename)[1] not in ['.png', '.jpg']: |
344 | + continue |
345 | + filename_path = Path(thumbnail_path) / Path(filename) |
346 | + # Create a thumbnail path in the zip/service file |
347 | + service_path = filename_path.relative_to(thumbnail_path_parent) |
348 | + write_list.append((filename_path, service_path)) |
349 | + # TODO: For images only copy the relevant thumbnails, not all thumbnails, skipped for now |
350 | for audio_path in item['service_item'].background_audio: |
351 | - if audio_path in write_list: |
352 | + audio_path_tuple = (audio_path, audio_path) |
353 | + if audio_path_tuple in write_list: |
354 | continue |
355 | - write_list.append(audio_path) |
356 | + write_list.append(audio_path_tuple) |
357 | return write_list, missing_list |
358 | |
359 | def save_file(self): |
360 | @@ -567,7 +596,6 @@ |
361 | |
362 | if not self._save_lite: |
363 | write_list, missing_list = self.get_write_file_list() |
364 | - |
365 | if missing_list: |
366 | self.application.set_normal_cursor() |
367 | title = translate('OpenLP.ServiceManager', 'Service File(s) Missing') |
368 | @@ -594,8 +622,8 @@ |
369 | service_content = json.dumps(service, cls=OpenLPJSONEncoder) |
370 | service_content_size = len(bytes(service_content, encoding='utf-8')) |
371 | total_size = service_content_size |
372 | - for file_item in write_list: |
373 | - total_size += file_item.stat().st_size |
374 | + for local_file_item, zip_file_item in write_list: |
375 | + total_size += local_file_item.stat().st_size |
376 | self.log_debug('ServiceManager.save_file - ZIP contents size is %i bytes' % total_size) |
377 | self.main_window.display_progress_bar(total_size) |
378 | try: |
379 | @@ -605,9 +633,9 @@ |
380 | zip_file.writestr('service_data.osj', service_content) |
381 | self.main_window.increment_progress_bar(service_content_size) |
382 | # Finally add all the listed media files. |
383 | - for write_path in write_list: |
384 | - zip_file.write(write_path, write_path) |
385 | - self.main_window.increment_progress_bar(write_path.stat().st_size) |
386 | + for local_file_item, zip_file_item in write_list: |
387 | + zip_file.write(str(local_file_item), str(zip_file_item)) |
388 | + self.main_window.increment_progress_bar(local_file_item.stat().st_size) |
389 | with suppress(FileNotFoundError): |
390 | file_path.unlink() |
391 | os.link(temp_file.name, file_path) |
392 | @@ -696,11 +724,30 @@ |
393 | service_data = None |
394 | self.application.set_busy_cursor() |
395 | try: |
396 | - with zipfile.ZipFile(file_path) as zip_file: |
397 | + # TODO: figure out a way to use the presentation thumbnails from the service file |
398 | + with zipfile.ZipFile(str(file_path)) as zip_file: |
399 | compressed_size = 0 |
400 | for zip_info in zip_file.infolist(): |
401 | compressed_size += zip_info.compress_size |
402 | self.main_window.display_progress_bar(compressed_size) |
403 | + # First find the osj-file to find out how to handle the file |
404 | + for zip_info in zip_file.infolist(): |
405 | + # The json file has been called 'service_data.osj' since OpenLP 3.0 |
406 | + if zip_info.filename == 'service_data.osj' or zip_info.filename.endswith('osj'): |
407 | + with zip_file.open(zip_info, 'r') as json_file: |
408 | + service_data = json_file.read() |
409 | + break |
410 | + if service_data: |
411 | + items = json.loads(service_data, cls=OpenLPJSONDecoder) |
412 | + else: |
413 | + raise ValidationError(msg='No service data found') |
414 | + # Extract the service file version |
415 | + for item in items: |
416 | + if 'openlp_core' in item: |
417 | + item = item['openlp_core'] |
418 | + self.servicefile_version = item.get('openlp-servicefile-version', 2) |
419 | + break |
420 | + print('service format version: %d' % self.servicefile_version) |
421 | for zip_info in zip_file.infolist(): |
422 | self.log_debug('Extract file: {name}'.format(name=zip_info.filename)) |
423 | # The json file has been called 'service_data.osj' since OpenLP 3.0 |
424 | @@ -708,19 +755,19 @@ |
425 | with zip_file.open(zip_info, 'r') as json_file: |
426 | service_data = json_file.read() |
427 | else: |
428 | - zip_info.filename = os.path.basename(zip_info.filename) |
429 | - zip_file.extract(zip_info, self.service_path) |
430 | + # Service files from earlier versions than 3 expects that all files are extracted |
431 | + # into the root of the service folder. |
432 | + if self.servicefile_version and self.servicefile_version < 3: |
433 | + zip_info.filename = os.path.basename(zip_info.filename.replace('/', os.path.sep)) |
434 | + zip_file.extract(zip_info, str(self.service_path)) |
435 | self.main_window.increment_progress_bar(zip_info.compress_size) |
436 | - if service_data: |
437 | - items = json.loads(service_data, cls=OpenLPJSONDecoder) |
438 | + # Handle the content |
439 | self.new_file() |
440 | self.process_service_items(items) |
441 | self.set_file_name(file_path) |
442 | self.main_window.add_recent_file(file_path) |
443 | self.set_modified(False) |
444 | Settings().setValue('servicemanager/last file', file_path) |
445 | - else: |
446 | - raise ValidationError(msg='No service data found') |
447 | except (NameError, OSError, ValidationError, zipfile.BadZipFile): |
448 | self.application.set_normal_cursor() |
449 | self.log_exception('Problem loading service file {name}'.format(name=file_path)) |
450 | @@ -752,9 +799,9 @@ |
451 | self.service_theme = theme |
452 | else: |
453 | if self._save_lite: |
454 | - service_item.set_from_service(item) |
455 | + service_item.set_from_service(item, version=self.servicefile_version) |
456 | else: |
457 | - service_item.set_from_service(item, self.service_path) |
458 | + service_item.set_from_service(item, self.service_path, self.servicefile_version) |
459 | service_item.validate_item(self.suffixes) |
460 | if service_item.is_capable(ItemCapabilities.OnLoadUpdate): |
461 | new_item = Registry().get(service_item.name).service_load(service_item) |
462 | @@ -1259,11 +1306,12 @@ |
463 | """ |
464 | Empties the service_path of temporary files on system exit. |
465 | """ |
466 | - for file_path in self.service_path.iterdir(): |
467 | - delete_file(file_path) |
468 | - audio_path = self.service_path / 'audio' |
469 | - if audio_path.exists(): |
470 | - shutil.rmtree(audio_path, True) |
471 | + for file_name in os.listdir(self.service_path): |
472 | + file_path = Path(self.service_path, file_name) |
473 | + if os.path.isdir(file_path): |
474 | + shutil.rmtree(file_path, True) |
475 | + else: |
476 | + delete_file(file_path) |
477 | |
478 | def on_theme_combo_box_selected(self, current_index): |
479 | """ |
480 | |
481 | === modified file 'openlp/plugins/images/lib/db.py' |
482 | --- openlp/plugins/images/lib/db.py 2019-04-13 13:00:22 +0000 |
483 | +++ openlp/plugins/images/lib/db.py 2019-09-15 09:05:37 +0000 |
484 | @@ -65,6 +65,7 @@ |
485 | * id |
486 | * group_id |
487 | * file_path |
488 | + * file_hash |
489 | """ |
490 | session, metadata = init_db(url) |
491 | |
492 | @@ -79,7 +80,8 @@ |
493 | image_filenames_table = Table('image_filenames', metadata, |
494 | Column('id', types.Integer(), primary_key=True), |
495 | Column('group_id', types.Integer(), ForeignKey('image_groups.id'), default=None), |
496 | - Column('file_path', PathType(), nullable=False) |
497 | + Column('file_path', PathType(), nullable=False), |
498 | + Column('file_hash', types.Unicode(128), nullable=False) |
499 | ) |
500 | |
501 | mapper(ImageGroups, image_groups_table) |
502 | |
503 | === modified file 'openlp/plugins/images/lib/mediaitem.py' |
504 | --- openlp/plugins/images/lib/mediaitem.py 2019-05-23 19:33:46 +0000 |
505 | +++ openlp/plugins/images/lib/mediaitem.py 2019-09-15 09:05:37 +0000 |
506 | @@ -25,7 +25,7 @@ |
507 | |
508 | from PyQt5 import QtCore, QtGui, QtWidgets |
509 | |
510 | -from openlp.core.common import delete_file, get_images_filter |
511 | +from openlp.core.common import delete_file, get_images_filter, sha256_file_hash |
512 | from openlp.core.common.applocation import AppLocation |
513 | from openlp.core.common.i18n import UiStrings, get_natural_key, translate |
514 | from openlp.core.common.path import create_paths |
515 | @@ -347,7 +347,7 @@ |
516 | :rtype: Path |
517 | """ |
518 | ext = image.file_path.suffix.lower() |
519 | - return self.service_path / '{name:d}{ext}'.format(name=image.id, ext=ext) |
520 | + return self.service_path / '{name:s}{ext}'.format(name=image.file_hash, ext=ext) |
521 | |
522 | def load_full_list(self, images, initial_load=False, open_group=None): |
523 | """ |
524 | @@ -492,6 +492,7 @@ |
525 | image_file = ImageFilenames() |
526 | image_file.group_id = group_id |
527 | image_file.file_path = image_path |
528 | + image_file.file_hash = sha256_file_hash(image_path) |
529 | self.manager.save_object(image_file) |
530 | self.main_window.increment_progress_bar() |
531 | if reload_list and image_paths: |
532 | |
533 | === modified file 'openlp/plugins/images/lib/upgrade.py' |
534 | --- openlp/plugins/images/lib/upgrade.py 2019-05-22 06:47:00 +0000 |
535 | +++ openlp/plugins/images/lib/upgrade.py 2019-09-15 09:05:37 +0000 |
536 | @@ -26,16 +26,17 @@ |
537 | import logging |
538 | from pathlib import Path |
539 | |
540 | -from sqlalchemy import Column, Table |
541 | +from sqlalchemy import Column, Table, types |
542 | |
543 | +from openlp.core.common import sha256_file_hash |
544 | from openlp.core.common.applocation import AppLocation |
545 | from openlp.core.common.db import drop_columns |
546 | -from openlp.core.common.json import OpenLPJSONEncoder |
547 | +from openlp.core.common.json import OpenLPJSONEncoder, OpenLPJSONDecoder |
548 | from openlp.core.lib.db import PathType, get_upgrade_op |
549 | |
550 | |
551 | log = logging.getLogger(__name__) |
552 | -__version__ = 2 |
553 | +__version__ = 3 |
554 | |
555 | |
556 | def upgrade_1(session, metadata): |
557 | @@ -68,3 +69,21 @@ |
558 | else: |
559 | op.drop_constraint('image_filenames', 'foreignkey') |
560 | op.drop_column('image_filenames', 'filenames') |
561 | + |
562 | + |
563 | +def upgrade_3(session, metadata): |
564 | + """ |
565 | + Version 3 upgrade - add sha256 hash |
566 | + """ |
567 | + log.debug('Starting upgrade_3 for adding sha256 hashes') |
568 | + old_table = Table('image_filenames', metadata, autoload=True) |
569 | + if 'file_hash' not in [col.name for col in old_table.c.values()]: |
570 | + op = get_upgrade_op(session) |
571 | + op.add_column('image_filenames', Column('file_hash', types.Unicode(128))) |
572 | + conn = op.get_bind() |
573 | + results = conn.execute('SELECT * FROM image_filenames') |
574 | + for row in results.fetchall(): |
575 | + file_path = json.loads(row.file_path, cls=OpenLPJSONDecoder) |
576 | + hash = sha256_file_hash(file_path) |
577 | + sql = 'UPDATE image_filenames SET file_hash = \'{hash}\' WHERE id = {id}'.format(hash=hash, id=row.id) |
578 | + conn.execute(sql) |
579 | |
580 | === modified file 'openlp/plugins/presentations/lib/mediaitem.py' |
581 | --- openlp/plugins/presentations/lib/mediaitem.py 2019-06-30 23:28:38 +0000 |
582 | +++ openlp/plugins/presentations/lib/mediaitem.py 2019-09-15 09:05:37 +0000 |
583 | @@ -359,7 +359,8 @@ |
584 | note = '' |
585 | if notes and len(notes) >= i: |
586 | note = notes[i - 1] |
587 | - service_item.add_from_command(str(path), file_name, str(thumbnail_path), title, note) |
588 | + service_item.add_from_command(str(path), file_name, str(thumbnail_path), title, note, |
589 | + doc.get_sha256_file_hash()) |
590 | i += 1 |
591 | thumbnail_path = doc.get_thumbnail_path(i, True) |
592 | doc.close_presentation() |
593 | |
594 | === modified file 'openlp/plugins/presentations/lib/presentationcontroller.py' |
595 | --- openlp/plugins/presentations/lib/presentationcontroller.py 2019-06-21 20:53:42 +0000 |
596 | +++ openlp/plugins/presentations/lib/presentationcontroller.py 2019-09-15 09:05:37 +0000 |
597 | @@ -25,12 +25,12 @@ |
598 | |
599 | from PyQt5 import QtCore |
600 | |
601 | -from openlp.core.common import md5_hash |
602 | +from openlp.core.common import md5_hash, sha256_file_hash |
603 | from openlp.core.common.applocation import AppLocation |
604 | from openlp.core.common.path import create_paths |
605 | from openlp.core.common.registry import Registry |
606 | from openlp.core.common.settings import Settings |
607 | -from openlp.core.lib import create_thumb, validate_thumb |
608 | +from openlp.core.lib import create_thumb |
609 | |
610 | |
611 | log = logging.getLogger(__name__) |
612 | @@ -98,6 +98,7 @@ |
613 | :rtype: None |
614 | """ |
615 | self.controller = controller |
616 | + self._sha256_file_hash = None |
617 | self._setup(document_path) |
618 | |
619 | def _setup(self, document_path): |
620 | @@ -146,6 +147,12 @@ |
621 | # get_temp_folder and PresentationPluginapp_startup is removed |
622 | if Settings().value('presentations/thumbnail_scheme') == 'md5': |
623 | folder = md5_hash(bytes(self.file_path)) |
624 | + elif Settings().value('presentations/thumbnail_scheme') == 'sha256file': |
625 | + if self._sha256_file_hash: |
626 | + folder = self._sha256_file_hash |
627 | + else: |
628 | + self._sha256_file_hash = sha256_file_hash(self.file_path) |
629 | + folder = self._sha256_file_hash |
630 | else: |
631 | folder = self.file_path.name |
632 | return Path(self.controller.thumbnail_folder, folder) |
633 | @@ -161,13 +168,20 @@ |
634 | # get_thumbnail_folder and PresentationPluginapp_startup is removed |
635 | if Settings().value('presentations/thumbnail_scheme') == 'md5': |
636 | folder = md5_hash(bytes(self.file_path)) |
637 | + elif Settings().value('presentations/thumbnail_scheme') == 'sha256file': |
638 | + if self._sha256_file_hash: |
639 | + folder = self._sha256_file_hash |
640 | + else: |
641 | + self._sha256_file_hash = sha256_file_hash(self.file_path) |
642 | + folder = self._sha256_file_hash |
643 | else: |
644 | folder = self.file_path.name |
645 | return Path(self.controller.temp_folder, folder) |
646 | |
647 | def check_thumbnails(self): |
648 | """ |
649 | - Check that the last thumbnail image exists and is valid and are more recent than the powerpoint file. |
650 | + Check that the last thumbnail image exists and is valid. It is not checked if presentation file is newer than |
651 | + thumbnail since the path is based on the file hash, so if it exists it is by definition up to date. |
652 | |
653 | :return: If the thumbnail is valid |
654 | :rtype: bool |
655 | @@ -175,7 +189,7 @@ |
656 | last_image_path = self.get_thumbnail_path(self.get_slide_count(), True) |
657 | if not (last_image_path and last_image_path.is_file()): |
658 | return False |
659 | - return validate_thumb(Path(self.file_path), Path(last_image_path)) |
660 | + return True |
661 | |
662 | def close_presentation(self): |
663 | """ |
664 | @@ -360,6 +374,17 @@ |
665 | notes_path = self.get_thumbnail_folder() / 'slideNotes{number:d}.txt'.format(number=slide_no) |
666 | notes_path.write_text(note) |
667 | |
668 | + def get_sha256_file_hash(self): |
669 | + """ |
670 | + Returns the sha256 file hash for the file. |
671 | + |
672 | + :return: The sha256 file hash |
673 | + :rtype: str |
674 | + """ |
675 | + if not self._sha256_file_hash: |
676 | + self._sha256_file_hash = sha256_file_hash(self.file_path) |
677 | + return self._sha256_file_hash |
678 | + |
679 | |
680 | class PresentationController(object): |
681 | """ |
682 | |
683 | === modified file 'openlp/plugins/presentations/presentationplugin.py' |
684 | --- openlp/plugins/presentations/presentationplugin.py 2019-05-18 04:56:15 +0000 |
685 | +++ openlp/plugins/presentations/presentationplugin.py 2019-09-15 09:05:37 +0000 |
686 | @@ -158,7 +158,7 @@ |
687 | for path in presentation_paths: |
688 | self.media_item.clean_up_thumbnails(path, clean_for_update=True) |
689 | self.media_item.list_view.clear() |
690 | - Settings().setValue('presentations/thumbnail_scheme', 'md5') |
691 | + Settings().setValue('presentations/thumbnail_scheme', 'sha256file') |
692 | self.media_item.validate_and_load(presentation_paths) |
693 | |
694 | @staticmethod |
695 | |
696 | === modified file 'tests/functional/openlp_core/lib/test_serviceitem.py' |
697 | --- tests/functional/openlp_core/lib/test_serviceitem.py 2019-05-22 06:47:00 +0000 |
698 | +++ tests/functional/openlp_core/lib/test_serviceitem.py 2019-09-15 09:05:37 +0000 |
699 | @@ -135,17 +135,19 @@ |
700 | assert 'Slide 2' == service_item.get_frame_title(1), '"Slide 2" has been returned as the title' |
701 | assert '' == service_item.get_frame_title(2), 'Blank has been returned as the title of slide 3' |
702 | |
703 | - def test_service_item_load_image_from_service(self): |
704 | + @patch('openlp.core.lib.serviceitem.sha256_file_hash') |
705 | + def test_service_item_load_image_from_service(self, mocked_sha256_file_hash): |
706 | """ |
707 | Test the Service Item - adding an image from a saved service |
708 | """ |
709 | # GIVEN: A new service item and a mocked add icon function |
710 | image_name = 'image_1.jpg' |
711 | test_file = TEST_PATH / image_name |
712 | - frame_array = {'path': test_file, 'title': image_name} |
713 | + frame_array = {'path': test_file, 'title': image_name, 'file_hash': 'fake_file_hash'} |
714 | |
715 | service_item = ServiceItem(None) |
716 | service_item.add_icon = MagicMock() |
717 | + mocked_sha256_file_hash.return_value = 'fake_file_hash' |
718 | |
719 | # WHEN: adding an image from a saved Service and mocked exists |
720 | line = convert_file_service_item(TEST_PATH, 'serviceitem_image_1.osj') |
721 | @@ -174,9 +176,11 @@ |
722 | assert service_item.is_capable(ItemCapabilities.CanAppend) is True, \ |
723 | 'This service item should be able to have new items added to it' |
724 | |
725 | + @patch('openlp.core.lib.serviceitem.sha256_file_hash') |
726 | @patch('openlp.core.lib.serviceitem.os.path.exists') |
727 | @patch('openlp.core.lib.serviceitem.AppLocation.get_section_data_path') |
728 | - def test_service_item_load_image_from_local_service(self, mocked_get_section_data_path, mocked_exists): |
729 | + def test_service_item_load_image_from_local_service(self, mocked_get_section_data_path, mocked_exists, |
730 | + mocked_sha256_file_hash): |
731 | """ |
732 | Test the Service Item - adding an image from a saved local service |
733 | """ |
734 | @@ -187,12 +191,13 @@ |
735 | image_name2 = 'image_2.jpg' |
736 | test_file1 = os.path.join('/home', 'openlp', image_name1) |
737 | test_file2 = os.path.join('/home', 'openlp', image_name2) |
738 | - frame_array1 = {'path': test_file1, 'title': image_name1} |
739 | - frame_array2 = {'path': test_file2, 'title': image_name2} |
740 | + frame_array1 = {'path': test_file1, 'file_hash': 'fake_file_hash', 'title': {'path': test_file1, 'title': image_name1}} |
741 | + frame_array2 = {'path': test_file2, 'file_hash': 'fake_file_hash', 'title': {'path': test_file2, 'title': image_name2}} |
742 | service_item = ServiceItem(None) |
743 | service_item.add_icon = MagicMock() |
744 | service_item2 = ServiceItem(None) |
745 | service_item2.add_icon = MagicMock() |
746 | + mocked_sha256_file_hash.return_value = 'fake_file_hash' |
747 | |
748 | # WHEN: adding an image from a saved Service and mocked exists |
749 | line = convert_file_service_item(TEST_PATH, 'serviceitem_image_2.osj') |
750 | @@ -215,8 +220,8 @@ |
751 | 'The frame path should match the full path to the image' |
752 | assert test_file2 == str(service_item2.get_frame_path(0)), \ |
753 | 'The frame path should match the full path to the image' |
754 | - assert image_name1 == service_item.get_frame_title(0), 'The 1st frame title should match the image name' |
755 | - assert image_name2 == service_item2.get_frame_title(0), 'The 2nd frame title should match the image name' |
756 | + assert image_name1 == service_item.get_frame_title(0)['title'], 'The 1st frame title should match the image name' |
757 | + assert image_name2 == service_item2.get_frame_title(0)['title'], 'The 2nd frame title should match the image name' |
758 | assert service_item.name == service_item.title.lower(), \ |
759 | 'The plugin name should match the display title, as there are > 1 Images' |
760 | assert service_item.is_image() is True, 'This service item should be of an "image" type' |
761 | @@ -229,7 +234,8 @@ |
762 | assert service_item.is_capable(ItemCapabilities.CanAppend) is True, \ |
763 | 'This service item should be able to have new items added to it' |
764 | |
765 | - def test_add_from_command_for_a_presentation(self): |
766 | + @patch('openlp.core.lib.serviceitem.sha256_file_hash') |
767 | + def test_add_from_command_for_a_presentation(self, mocked_sha256_file_hash): |
768 | """ |
769 | Test the Service Item - adding a presentation |
770 | """ |
771 | @@ -249,7 +255,8 @@ |
772 | assert service_item.service_item_type == ServiceItemType.Command, 'It should be a Command' |
773 | assert service_item.get_frames()[0] == frame, 'Frames should match' |
774 | |
775 | - def test_add_from_command_without_display_title_and_notes(self): |
776 | + @patch('openlp.core.lib.serviceitem.sha256_file_hash') |
777 | + def test_add_from_comamnd_without_display_title_and_notes(self, mocked_sha256_file_hash): |
778 | """ |
779 | Test the Service Item - add from command, but not presentation |
780 | """ |
781 | @@ -267,13 +274,16 @@ |
782 | assert service_item.service_item_type == ServiceItemType.Command, 'It should be a Command' |
783 | assert service_item.get_frames()[0] == frame, 'Frames should match' |
784 | |
785 | - @patch(u'openlp.core.lib.serviceitem.ServiceItem.image_manager') |
786 | + @patch('openlp.core.lib.serviceitem.ServiceItem.image_manager') |
787 | @patch('openlp.core.lib.serviceitem.AppLocation.get_section_data_path') |
788 | - def test_add_from_command_for_a_presentation_thumb(self, mocked_get_section_data_path, mocked_image_manager): |
789 | + @patch('openlp.core.lib.serviceitem.sha256_file_hash') |
790 | + def test_add_from_command_for_a_presentation_thumb(self, mocked_sha256_file_hash, mocked_get_section_data_path, |
791 | + mocked_image_manager): |
792 | """ |
793 | Test the Service Item - adding a presentation, updating the thumb path & adding the thumb to image_manager |
794 | """ |
795 | # GIVEN: A service item, a mocked AppLocation and presentation data |
796 | + mocked_sha256_file_hash.return_value = 'fake_file_hash' |
797 | mocked_get_section_data_path.return_value = Path('mocked') / 'section' / 'path' |
798 | service_item = ServiceItem(None) |
799 | service_item.add_capability(ItemCapabilities.HasThumbnails) |
800 | @@ -283,8 +293,7 @@ |
801 | thumb = Path('tmp') / 'test' / 'thumb.png' |
802 | display_title = 'DisplayTitle' |
803 | notes = 'Note1\nNote2\n' |
804 | - expected_thumb_path = Path('mocked') / 'section' / 'path' / 'thumbnails' / \ |
805 | - md5_hash(str(TEST_PATH / presentation_name).encode('utf8')) / 'thumb.png' |
806 | + expected_thumb_path = Path('mocked') / 'section' / 'path' / 'thumbnails' / 'fake_file_hash' / 'thumb.png' |
807 | frame = {'title': presentation_name, 'image': str(expected_thumb_path), 'path': str(TEST_PATH), |
808 | 'display_title': display_title, 'notes': notes, 'thumbnail': str(expected_thumb_path)} |
809 | |
810 | @@ -296,7 +305,8 @@ |
811 | assert service_item.get_frames()[0] == frame, 'Frames should match' |
812 | # assert 1 == mocked_image_manager.add_image.call_count, 'image_manager should be used' |
813 | |
814 | - def test_service_item_load_optical_media_from_service(self): |
815 | + @patch('openlp.core.lib.serviceitem.sha256_file_hash') |
816 | + def test_service_item_load_optical_media_from_service(self, mocked_sha256_file_hash): |
817 | """ |
818 | Test the Service Item - load an optical media item |
819 | """ |
820 | |
821 | === modified file 'tests/functional/openlp_plugins/images/test_lib.py' |
822 | --- tests/functional/openlp_plugins/images/test_lib.py 2019-05-22 06:47:00 +0000 |
823 | +++ tests/functional/openlp_plugins/images/test_lib.py 2019-09-15 09:05:37 +0000 |
824 | @@ -101,8 +101,9 @@ |
825 | assert self.media_item.manager.save_object.call_count == 0, \ |
826 | 'The save_object() method should not have been called' |
827 | |
828 | + @patch('openlp.plugins.images.lib.mediaitem.sha256_file_hash') |
829 | @patch('openlp.plugins.images.lib.mediaitem.ImageMediaItem.load_full_list') |
830 | - def test_save_new_images_list_single_image_with_reload(self, mocked_load_full_list): |
831 | + def test_save_new_images_list_single_image_with_reload(self, mocked_load_full_list, mocked_sha256_file_hash): |
832 | """ |
833 | Test that the save_new_images_list() calls load_full_list() when reload_list is set to True |
834 | """ |
835 | @@ -110,6 +111,7 @@ |
836 | image_list = [Path('test_image.jpg')] |
837 | ImageFilenames.file_path = None |
838 | self.media_item.manager = MagicMock() |
839 | + mocked_sha256_file_hash.return_value = 'fake_file_hash' |
840 | |
841 | # WHEN: We run save_new_images_list with reload_list=True |
842 | self.media_item.save_new_images_list(image_list, reload_list=True) |
843 | @@ -120,14 +122,16 @@ |
844 | # CLEANUP: Remove added attribute from ImageFilenames |
845 | delattr(ImageFilenames, 'file_path') |
846 | |
847 | + @patch('openlp.plugins.images.lib.mediaitem.sha256_file_hash') |
848 | @patch('openlp.plugins.images.lib.mediaitem.ImageMediaItem.load_full_list') |
849 | - def test_save_new_images_list_single_image_without_reload(self, mocked_load_full_list): |
850 | + def test_save_new_images_list_single_image_without_reload(self, mocked_load_full_list, mocked_sha256_file_hash): |
851 | """ |
852 | Test that the save_new_images_list() doesn't call load_full_list() when reload_list is set to False |
853 | """ |
854 | # GIVEN: A list with 1 image and a mocked out manager |
855 | image_list = [Path('test_image.jpg')] |
856 | self.media_item.manager = MagicMock() |
857 | + mocked_sha256_file_hash.return_value = 'fake_file_hash' |
858 | |
859 | # WHEN: We run save_new_images_list with reload_list=False |
860 | self.media_item.save_new_images_list(image_list, reload_list=False) |
861 | @@ -135,14 +139,16 @@ |
862 | # THEN: load_full_list() should not have been called |
863 | assert mocked_load_full_list.call_count == 0, 'load_full_list() should not have been called' |
864 | |
865 | + @patch('openlp.plugins.images.lib.mediaitem.sha256_file_hash') |
866 | @patch('openlp.plugins.images.lib.mediaitem.ImageMediaItem.load_full_list') |
867 | - def test_save_new_images_list_multiple_images(self, mocked_load_full_list): |
868 | + def test_save_new_images_list_multiple_images(self, mocked_load_full_list, mocked_sha256_file_hash): |
869 | """ |
870 | Test that the save_new_images_list() saves all images in the list |
871 | """ |
872 | # GIVEN: A list with 3 images |
873 | image_list = [Path('test_image_1.jpg'), Path('test_image_2.jpg'), Path('test_image_3.jpg')] |
874 | self.media_item.manager = MagicMock() |
875 | + mocked_sha256_file_hash.return_value = 'fake_file_hash' |
876 | |
877 | # WHEN: We run save_new_images_list with the list of 3 images |
878 | self.media_item.save_new_images_list(image_list, reload_list=False) |
879 | @@ -151,14 +157,16 @@ |
880 | assert self.media_item.manager.save_object.call_count == 3, \ |
881 | 'load_full_list() should have been called three times' |
882 | |
883 | + @patch('openlp.plugins.images.lib.mediaitem.sha256_file_hash') |
884 | @patch('openlp.plugins.images.lib.mediaitem.ImageMediaItem.load_full_list') |
885 | - def test_save_new_images_list_other_objects_in_list(self, mocked_load_full_list): |
886 | + def test_save_new_images_list_other_objects_in_list(self, mocked_load_full_list, mocked_sha256_file_hash): |
887 | """ |
888 | Test that the save_new_images_list() ignores everything in the provided list except strings |
889 | """ |
890 | # GIVEN: A list with images and objects |
891 | image_list = [Path('test_image_1.jpg'), None, True, ImageFilenames(), Path('test_image_2.jpg')] |
892 | self.media_item.manager = MagicMock() |
893 | + mocked_sha256_file_hash.return_value = 'fake_file_hash' |
894 | |
895 | # WHEN: We run save_new_images_list with the list of images and objects |
896 | self.media_item.save_new_images_list(image_list, reload_list=False) |
897 | @@ -217,12 +225,15 @@ |
898 | returned_object1 = ImageFilenames() |
899 | returned_object1.id = 1 |
900 | returned_object1.file_path = Path('/', 'tmp', 'test_file_1.jpg') |
901 | + returned_object1.file_hash = 'fake_file_hash' |
902 | returned_object2 = ImageFilenames() |
903 | returned_object2.id = 2 |
904 | returned_object2.file_path = Path('/', 'tmp', 'test_file_2.jpg') |
905 | + returned_object2.file_hash = 'fake_file_hash' |
906 | returned_object3 = ImageFilenames() |
907 | returned_object3.id = 3 |
908 | returned_object3.file_path = Path('/', 'tmp', 'test_file_3.jpg') |
909 | + returned_object3.file_hash = 'fake_file_hash' |
910 | return [returned_object1, returned_object2, returned_object3] |
911 | if args[1] == ImageGroups and args[2]: |
912 | # Change the parent_id that is matched so we don't get into an endless loop |
913 | @@ -233,9 +244,10 @@ |
914 | return [returned_object1] |
915 | return [] |
916 | |
917 | + @patch('openlp.plugins.images.lib.mediaitem.sha256_file_hash') |
918 | @patch('openlp.plugins.images.lib.mediaitem.delete_file') |
919 | @patch('openlp.plugins.images.lib.mediaitem.check_item_selected') |
920 | - def test_on_delete_click(self, mocked_check_item_selected, mocked_delete_file): |
921 | + def test_on_delete_click(self, mocked_check_item_selected, mocked_delete_file, mocked_sha256_file_hash): |
922 | """ |
923 | Test that on_delete_click() works |
924 | """ |
925 | @@ -245,6 +257,7 @@ |
926 | test_image.id = 1 |
927 | test_image.group_id = 1 |
928 | test_image.file_path = Path('imagefile.png') |
929 | + test_image.file_hash = 'fake_file_hash' |
930 | self.media_item.manager = MagicMock() |
931 | self.media_item.service_path = Path() |
932 | self.media_item.list_view = MagicMock() |
933 | @@ -252,6 +265,7 @@ |
934 | mocked_row_item.data.return_value = test_image |
935 | mocked_row_item.text.return_value = '' |
936 | self.media_item.list_view.selectedItems.return_value = [mocked_row_item] |
937 | + mocked_sha256_file_hash.return_value = 'fake_file_hash' |
938 | |
939 | # WHEN: Calling on_delete_click |
940 | self.media_item.on_delete_click() |
941 | |
942 | === modified file 'tests/interfaces/openlp_core/widgets/test_views.py' |
943 | --- tests/interfaces/openlp_core/widgets/test_views.py 2019-04-13 13:00:22 +0000 |
944 | +++ tests/interfaces/openlp_core/widgets/test_views.py 2019-09-15 09:05:37 +0000 |
945 | @@ -79,7 +79,8 @@ |
946 | # THEN: The number of the current item should be -1. |
947 | assert self.preview_widget.current_slide_number() == -1, 'The slide number should be -1.' |
948 | |
949 | - def test_replace_service_item(self): |
950 | + @patch('openlp.core.lib.serviceitem.sha256_file_hash') |
951 | + def test_replace_service_item(self, mocked_sha256_file_hash): |
952 | """ |
953 | Test item counts and current number with a service item. |
954 | """ |
955 | @@ -94,7 +95,8 @@ |
956 | assert self.preview_widget.slide_count() == 2, 'The slide count should be 2.' |
957 | assert self.preview_widget.current_slide_number() == 1, 'The current slide number should be 1.' |
958 | |
959 | - def test_change_slide(self): |
960 | + @patch('openlp.core.lib.serviceitem.sha256_file_hash') |
961 | + def test_change_slide(self, mocked_sha256_file_hash): |
962 | """ |
963 | Test the change_slide method. |
964 | """ |
Linux tests failed, please see https:/ /ci.openlp. io/job/ MP-02-Linux_ Tests/170/ for more details