Skip to content

Commit

Permalink
Merge pull request #1253 from girder/replace-thumbnail-endpoint
Browse files Browse the repository at this point in the history
Add an endpoint to make it easier to replace thumbnails
  • Loading branch information
manthey authored Aug 2, 2023
2 parents c9a5b37 + bdf61c1 commit fa247f3
Show file tree
Hide file tree
Showing 3 changed files with 98 additions and 4 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
- Adjust the bounds on non geospatial leaflet maps ([#1249](../../pull/1249))
- Allow hiding metadata on item pages ([#1250](../../pull/1250))
- Allow caching rounded histograms ([#1252](../../pull/1252))
- Add an endpoint to make it easier to replace thumbnails ([#1253](../../pull/1253))

### Bug Fixes
- Guard against ICC profiles that won't parse ([#1245](../../pull/1245))
Expand Down
74 changes: 70 additions & 4 deletions girder/girder_large_image/rest/tiles.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#############################################################################

import hashlib
import io
import math
import os
import pathlib
Expand All @@ -33,6 +34,7 @@
from girder.exceptions import RestException
from girder.models.file import File
from girder.models.item import Item
from girder.models.upload import Upload
from girder.utility.progress import setResponseTimeLimit
from large_image.cache_util import strhash
from large_image.constants import TileInputUnits, TileOutputMimeTypes
Expand Down Expand Up @@ -150,6 +152,7 @@ def __init__(self, apiRoot):
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('POST', (':itemId', 'tiles', 'thumbnails'), self.addTilesThumbnails)
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 @@ -1429,11 +1432,74 @@ def listTilesThumbnails(self, item):
@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)
.param('keep', 'Number of thumbnails to keep. Ignored if a key is '
'specified.', dataType='integer', required=False,
default=10000)
.param('key', 'A specific key to delete', required=False)
.param('thumbnail', 'If a key is specified, true if the key is a '
'thumbnail; false if the key is a data record',
dataType='boolean', required=False)
.errorResponse('ID was invalid.')
.errorResponse('Read access was denied for the item.', 403)
)
@access.admin(scope=TokenScope.DATA_WRITE)
def deleteTilesThumbnails(self, item, keep):
return self.imageItemModel.removeThumbnailFiles(item, keep=keep or 0)
def deleteTilesThumbnails(self, item, keep, key=None, thumbnail=True):
if not key:
return self.imageItemModel.removeThumbnailFiles(item, keep=keep or 0)
thumbnail = str(thumbnail).lower() != 'false'
query = {
'attachedToType': 'item',
'attachedToId': item['_id'],
'isLargeImageThumbnail' if thumbnail is not False else 'isLargeImageData': True,
'thumbnailKey': key,
}
file = File().findOne(query)
if file:
File().remove(file)
return [file]

@autoDescribeRoute(
Description('Associate or replace a thumbnail or data file with a large_image items.')
.responseClass('File')
.modelParam('itemId', model=Item, level=AccessType.WRITE)
.param('key', 'A specific key to delete', required=True)
.param('thumbnail', 'If a key is specified, true if the key is a '
'thumbnail; false if the key is a data record',
dataType='boolean', required=False)
.param('mimeType', 'The MIME type of the file.', required=False)
.param('data', 'An image or data block to associated with the large_image item.',
paramType='body', dataType='binary')
.consumes('application/octet-stream')
.errorResponse('ID was invalid.')
.errorResponse('Read access was denied for the item.', 403)
)
@access.user(scope=TokenScope.DATA_WRITE)
def addTilesThumbnails(self, item, key, mimeType, thumbnail=False, data=None):
user = self.getCurrentUser()
thumbnail = str(thumbnail).lower() != 'false'
query = {
'attachedToType': 'item',
'attachedToId': item['_id'],
'isLargeImageThumbnail' if thumbnail is not False else 'isLargeImageData': True,
'thumbnailKey': key,
}
file = File().findOne(query)
if file:
File().remove(file)
data = cherrypy.request.body.read()
try:
import magic
mimeType = magic.from_buffer(data, mime=True) or mimeType
except Exception:
pass
mimeType = mimeType or 'application/octet-stream'
datafile = Upload().uploadFromFile(
io.BytesIO(data), size=len(data),
name='_largeImageThumbnail', parentType='item', parent=item,
user=user, mimeType=mimeType, attachParent=True)
datafile.update({
'isLargeImageThumbnail' if thumbnail is not False else 'isLargeImageData': True,
'thumbnailKey': key,
})
datafile = File().save(datafile)
return datafile
27 changes: 27 additions & 0 deletions girder/test_girder/test_tiles_rest.py
Original file line number Diff line number Diff line change
Expand Up @@ -1499,3 +1499,30 @@ def testThumbnailMaintenance(server, admin, fsAssetstore):
resp = server.request(path='/item/%s/tiles/thumbnails' % itemId, user=admin)
assert utilities.respStatus(resp) == 200
assert len(resp.json) == 0

# Get a thumbnail
resp = server.request(path='/item/%s/tiles/thumbnail' % itemId,
user=admin, isJson=False)
assert utilities.respStatus(resp) == 200
thumb = utilities.getBody(resp, text=False)
resp = server.request(path='/item/%s/tiles/thumbnails' % itemId, user=admin)
assert utilities.respStatus(resp) == 200
# Ask to delete it specifically
key = resp.json[0]['thumbnailKey']
resp = server.request(
path='/item/%s/tiles/thumbnails' % itemId, method='DELETE', user=admin,
params={'key': key})
assert utilities.respStatus(resp) == 200
assert len(resp.json) == 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
# Add it back
resp = server.request(
path='/item/%s/tiles/thumbnails' % itemId, method='POST', user=admin,
params={'key': key}, body=thumb, type='application/octet-stream')

resp = server.request(path='/item/%s/tiles/thumbnails' % itemId, user=admin)
assert utilities.respStatus(resp) == 200
assert len(resp.json) == 1

0 comments on commit fa247f3

Please sign in to comment.