diff --git a/girder/girder_large_image/rest/large_image_resource.py b/girder/girder_large_image/rest/large_image_resource.py index 0f6557b21..d600499c5 100644 --- a/girder/girder_large_image/rest/large_image_resource.py +++ b/girder/girder_large_image/rest/large_image_resource.py @@ -41,6 +41,7 @@ from girder.models.folder import Folder from girder.models.item import Item from girder.models.setting import Setting +from girder.utility import toBool from large_image import cache_util from large_image.exceptions import TileGeneralError, TileSourceError @@ -449,67 +450,108 @@ def _deleteCachedImages(self, spec, associatedImages=False, imageKey=None): @access.user(scope=TokenScope.DATA_WRITE) @autoDescribeRoute( - Description('Create new large images for all items within a folder.') - .notes('Does not work for items with multiple files and skips over items with ' - 'existing or unfinished large images.') - .modelParam('id', 'The ID of the folder.', model=Folder, level=AccessType.WRITE, - required=True) - .param('force', 'Whether creation job(s) should be forced for each large image.', - required=False, default=False, dataType='boolean') - .param('localJobs', 'Whether the job(s) created should be local.', required=False, - default=False, dataType='boolean') - .param('recurse', 'Whether child folders should be recursed.', required=False, - default=False, dataType='boolean') + Description('Create large images for all items within a folder.') + .notes('Does not work for new items with multiple files.') + .modelParam('id', 'The ID of the folder.', model=Folder, + level=AccessType.WRITE, required=True) + .param('createJobs', 'If true, a job will be used to create the image ' + 'when needed; if always, a job will always be used; if false, ' + 'a job will never be used, creating a version of the image in ' + 'a preferred format.', dataType='string', default='false', + required=False, enum=['true', 'false', 'always']) + .param('localJobs', 'If true, run each creation job locally; if false, ' + 'run via the remote worker.', dataType='boolean', default='false', + required=False) + .param('recurse', 'If true, items in child folders will also be checked.', + dataType='boolean', default=False, required=False) + .param('cancelJobs', 'If true, unfinished large image job(s) associated ' + 'with items in the folder will be canceled, then a new large ' + 'image created; if false, items with an unfinished large image ' + 'will be skipped.', dataType='boolean', default=False, required=False) + .param('redoExisting', 'If true, existing large images should be removed and ' + 'recreated. Otherwise they will be skipped.', dataType='boolean', + default=False, required=False) .errorResponse('ID was invalid.') .errorResponse('Write access was denied for the folder.', 403), ) - def createLargeImages(self, folder, params): + def createLargeImages(self, folder, createJobs, localJobs, recurse, cancelJobs, redoExisting): user = self.getCurrentUser() - createJobs = 'always' if self.boolParam('force', params, default=False) else True - return self.createImagesRecurseOption(folder=folder, createJobs=createJobs, user=user, - recurse=params.get('recurse'), - localJobs=params.get('localJobs')) - - def createImagesRecurseOption(self, folder, createJobs, user, recurse, localJobs): - result = {'childFoldersRecursed': 0, - 'itemsSkipped': 0, - 'largeImagesCreated': 0, - 'largeImagesRemovedAndRecreated': 0, - 'totalItems': 0} + if createJobs != 'always': + createJobs = toBool(createJobs) + return self._createLargeImagesRecurse( + folder=folder, user=user, recurse=recurse, createJobs=createJobs, + localJobs=localJobs, cancelJobs=cancelJobs, redo=redoExisting) + + def _createLargeImagesRecurse( + self, folder, user, recurse, createJobs, localJobs, cancelJobs, + redo, result=None): + if result is None: + result = {'childFoldersRecursed': 0, + 'itemsSkipped': 0, + 'jobsCanceled': 0, + 'jobsFailedToCancel': 0, + 'largeImagesCreated': 0, + 'largeImagesNotCreated': 0, + 'largeImagesRemovedAndRecreated': 0, + 'largeImagesRemovedAndNotRecreated': 0, + 'totalItems': 0} if recurse: for childFolder in Folder().childFolders(parent=folder, parentType='folder'): result['childFoldersRecursed'] += 1 - childResult = self.createImagesRecurseOption(folder=childFolder, - createJobs=createJobs, user=user, - recurse=recurse, localJobs=localJobs) - for key in childResult: - result[key] += childResult[key] + self.createLargeImagesRecurse( + childFolder, user, recurse, createJobs, localJobs, + cancelJobs, redo, result) for item in Folder().childItems(folder=folder): result['totalItems'] += 1 - if item.get('largeImage'): - if item['largeImage'].get('expected'): + self._createLargeImagesItem( + item, user, createJobs, localJobs, cancelJobs, redo, result) + return result + + def _createLargeImagesItem( + self, item, user, createJobs, localJobs, cancelJobs, redo, result): + if item.get('largeImage'): + previousFileId = item['largeImage'].get('originalId', item['largeImage']['fileId']) + if item['largeImage'].get('expected'): + if not cancelJobs: result['itemsSkipped'] += 1 - else: - try: - ImageItem().getMetadata(item) - result['itemsSkipped'] += 1 - continue - except (TileSourceError, KeyError): - previousFileId = item['largeImage'].get('originalId', - item['largeImage']['fileId']) - ImageItem().delete(item) - ImageItem().createImageItem(item, File().load(user=user, id=previousFileId), - createJob=createJobs, localJob=localJobs) - result['largeImagesRemovedAndRecreated'] += 1 + return + job = Job().load(item['largeImage']['jobId'], force=True) + if job and job.get('status') in { + JobStatus.QUEUED, JobStatus.RUNNING, JobStatus.INACTIVE}: + job = Job().cancelJob(job) + if job and job.get('status') in { + JobStatus.QUEUED, JobStatus.RUNNING, JobStatus.INACTIVE}: + result['jobsFailedToCancel'] += 1 + result['itemsSkipped'] += 1 + return + result['jobsCanceled'] += 1 else: - files = list(Item().childFiles(item=item, limit=2)) - if len(files) == 1: - ImageItem().createImageItem(item, files[0], createJob=createJobs, - localJob=localJobs) + try: + ImageItem().getMetadata(item) + if not redo: + result['itemsSkipped'] += 1 + return + except (TileSourceError, KeyError): + pass + ImageItem().delete(item) + try: + ImageItem().createImageItem( + item, File().load(user=user, id=previousFileId), + createJob=createJobs, localJob=localJobs) + result['largeImagesRemovedAndRecreated'] += 1 + except Exception: + result['largeImagesRemovedAndNotRecreated'] += 1 + else: + files = list(Item().childFiles(item=item, limit=2)) + if len(files) == 1: + try: + ImageItem().createImageItem( + item, files[0], createJob=createJobs, localJob=localJobs) result['largeImagesCreated'] += 1 - else: - result['itemsSkipped'] += 1 - return result + except Exception: + result['largeImagesNotCreated'] += 1 + else: + result['itemsSkipped'] += 1 @describeRoute( Description('Remove large images from items where the large image job '