Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add options to cancel jobs, redo existing large images, and not force jobs #1586

Merged
merged 7 commits into from
Aug 6, 2024
140 changes: 91 additions & 49 deletions girder/girder_large_image/rest/large_image_resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -449,67 +450,108 @@

@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(

Check warning on line 501 in girder/girder_large_image/rest/large_image_resource.py

View check run for this annotation

Codecov / codecov/patch

girder/girder_large_image/rest/large_image_resource.py#L501

Added line #L501 was not covered by tests
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)

Check warning on line 518 in girder/girder_large_image/rest/large_image_resource.py

View check run for this annotation

Codecov / codecov/patch

girder/girder_large_image/rest/large_image_resource.py#L517-L518

Added lines #L517 - L518 were not covered by tests
if job and job.get('status') in {
JobStatus.QUEUED, JobStatus.RUNNING, JobStatus.INACTIVE}:
job = Job().cancelJob(job)

Check warning on line 521 in girder/girder_large_image/rest/large_image_resource.py

View check run for this annotation

Codecov / codecov/patch

girder/girder_large_image/rest/large_image_resource.py#L521

Added line #L521 was not covered by tests
if job and job.get('status') in {
JobStatus.QUEUED, JobStatus.RUNNING, JobStatus.INACTIVE}:
result['jobsFailedToCancel'] += 1
result['itemsSkipped'] += 1
return
result['jobsCanceled'] += 1

Check warning on line 527 in girder/girder_large_image/rest/large_image_resource.py

View check run for this annotation

Codecov / codecov/patch

girder/girder_large_image/rest/large_image_resource.py#L524-L527

Added lines #L524 - L527 were not covered by tests
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

Check warning on line 543 in girder/girder_large_image/rest/large_image_resource.py

View check run for this annotation

Codecov / codecov/patch

girder/girder_large_image/rest/large_image_resource.py#L542-L543

Added lines #L542 - L543 were not covered by tests
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

Check warning on line 552 in girder/girder_large_image/rest/large_image_resource.py

View check run for this annotation

Codecov / codecov/patch

girder/girder_large_image/rest/large_image_resource.py#L551-L552

Added lines #L551 - L552 were not covered by tests
else:
result['itemsSkipped'] += 1

Check warning on line 554 in girder/girder_large_image/rest/large_image_resource.py

View check run for this annotation

Codecov / codecov/patch

girder/girder_large_image/rest/large_image_resource.py#L554

Added line #L554 was not covered by tests

@describeRoute(
Description('Remove large images from items where the large image job '
Expand Down