Skip to content

Commit

Permalink
Merge pull request #1586 from girder/cancel-pending-jobs
Browse files Browse the repository at this point in the history
Add options to cancel jobs, redo existing large images, and not force jobs
  • Loading branch information
manthey authored Aug 6, 2024
2 parents 010234d + 9e38616 commit 0d2b1ee
Showing 1 changed file with 91 additions and 49 deletions.
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 @@ 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 '
Expand Down

0 comments on commit 0d2b1ee

Please sign in to comment.