Skip to content

Commit

Permalink
Merge pull request #980 from girder/thumbnail-maintenance
Browse files Browse the repository at this point in the history
Add some admin endpoints to check thumbnails
  • Loading branch information
manthey authored Oct 7, 2022
2 parents c1b2dd5 + 7ba3986 commit c8cb518
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 4 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
- Add metadata and annotation metadata search modes to Girder ([#974](../../pull/974))
- Add the ability to show annotation metadata in item annotation lists ([#977](../../pull/977))
- Support ETAG in annotation rest responses for better browser caching ([#978](../../pull/978))
- Thumbnail maintenance endpoints ([#979](../../pull/979))

## 1.17.0

Expand Down
17 changes: 13 additions & 4 deletions girder/girder_large_image/models/image_item.py
Original file line number Diff line number Diff line change
Expand Up @@ -416,7 +416,8 @@ def _getAndCacheImageOrData(
logger.warning('Could not cache data for large image')
return imageData, imageMime

def removeThumbnailFiles(self, item, keep=0, sort=None, imageKey=None, **kwargs):
def removeThumbnailFiles(self, item, keep=0, sort=None, imageKey=None,
onlyList=False, **kwargs):
"""
Remove all large image thumbnails from an item.
Expand All @@ -426,6 +427,8 @@ def removeThumbnailFiles(self, item, keep=0, sort=None, imageKey=None, **kwargs)
sort order are kept.
:param imageKey: None for the basic thumbnail, otherwise an associated
imageKey.
:param onlyList: if True, return a list of known thumbnails or data
files that would be removed, but don't remove them.
:param kwargs: additional parameters to determine which files to
remove.
:returns: a tuple of (the number of files before removal, the number of
Expand All @@ -436,6 +439,9 @@ def removeThumbnailFiles(self, item, keep=0, sort=None, imageKey=None, **kwargs)
keys.append('isLargeImageData')
if not sort:
sort = [('_id', SortDir.DESCENDING)]
results = []
present = 0
removed = 0
for key in keys:
query = {
'attachedToType': 'item',
Expand All @@ -448,15 +454,18 @@ def removeThumbnailFiles(self, item, keep=0, sort=None, imageKey=None, **kwargs)
else:
query['thumbnailKey'] = {'$regex': '"imageKey":"%s"' % imageKey}
query.update(kwargs)
present = 0
removed = 0
for file in File().find(query, sort=sort):
present += 1
if keep > 0:
keep -= 1
continue
File().remove(file)
if onlyList:
results.append(file)
else:
File().remove(file)
removed += 1
if onlyList:
return results
return (present, removed)

def getRegion(self, item, **kwargs):
Expand Down
24 changes: 24 additions & 0 deletions girder/girder_large_image/rest/tiles.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,8 @@ def __init__(self, apiRoot):
apiRoot.item.route('GET', (':itemId', 'tiles'), self.getTilesInfo)
apiRoot.item.route('DELETE', (':itemId', 'tiles'), self.deleteTiles)
apiRoot.item.route('GET', (':itemId', 'tiles', 'thumbnail'), self.getTilesThumbnail)
apiRoot.item.route('GET', (':itemId', 'tiles', 'thumbnails'), self.listTilesThumbnails)
apiRoot.item.route('DELETE', (':itemId', 'tiles', 'thumbnails'), self.deleteTilesThumbnails)
apiRoot.item.route('GET', (':itemId', 'tiles', 'region'), self.getTilesRegion)
apiRoot.item.route('GET', (':itemId', 'tiles', 'tile_frames'), self.tileFrames)
apiRoot.item.route('GET', (':itemId', 'tiles', 'tile_frames', 'quad_info'),
Expand Down Expand Up @@ -1399,3 +1401,25 @@ def tileFramesQuadInfo(self, item, params):
result['scheduledJob'] = str(self.imageItemModel._scheduleTileFrames(
item, needed, self.getCurrentUser())['_id'])
return result

@autoDescribeRoute(
Description('List all thumbnail and data files associated with a large_image item.')
.modelParam('itemId', model=Item, level=AccessType.READ)
.errorResponse('ID was invalid.')
.errorResponse('Read access was denied for the item.', 403)
)
@access.admin
def listTilesThumbnails(self, item):
return self.imageItemModel.removeThumbnailFiles(item, onlyList=True)

@autoDescribeRoute(
Description('Delete thumbnail and data files associated with a large_image item.')
.modelParam('itemId', model=Item, level=AccessType.READ)
.param('keep', 'Number of thumbnails to keep.', dataType='integer',
required=False, default=10000)
.errorResponse('ID was invalid.')
.errorResponse('Read access was denied for the item.', 403)
)
@access.admin
def deleteTilesThumbnails(self, item, keep):
return self.imageItemModel.removeThumbnailFiles(item, keep=keep or 0)
33 changes: 33 additions & 0 deletions girder/test_girder/test_tiles_rest.py
Original file line number Diff line number Diff line change
Expand Up @@ -1448,3 +1448,36 @@ def testTileFramesQuadInfo(server, admin, fsAssetstore):
assert utilities.respStatus(resp) == 200
assert 'cached' in resp.json
assert resp.json['cached'][0] is True


@pytest.mark.usefixtures('unbindLargeImage')
@pytest.mark.plugin('large_image')
def testThumbnailMaintenance(server, admin, fsAssetstore):
file = utilities.uploadExternalFile(
'sample_image.ptif', admin, fsAssetstore)
itemId = str(file['itemId'])
# Get a thumbnail
resp = server.request(path='/item/%s/tiles/thumbnail' % itemId,
user=admin, isJson=False)
assert utilities.respStatus(resp) == 200

# Check that we list a thumbnail
resp = server.request(path='/item/%s/tiles/thumbnails' % itemId, user=admin)
assert utilities.respStatus(resp) == 200
assert len(resp.json) == 1

# Ask to delete it
resp = server.request(path='/item/%s/tiles/thumbnails' % itemId, method='DELETE', user=admin)
assert utilities.respStatus(resp) == 200
assert resp.json == [1, 0]

resp = server.request(
path='/item/%s/tiles/thumbnails' % itemId, method='DELETE', user=admin,
params={'keep': 0})
assert utilities.respStatus(resp) == 200
assert resp.json == [1, 1]

# It should be gone
resp = server.request(path='/item/%s/tiles/thumbnails' % itemId, user=admin)
assert utilities.respStatus(resp) == 200
assert len(resp.json) == 0

0 comments on commit c8cb518

Please sign in to comment.