Skip to content

Commit

Permalink
Merge pull request #1534 from dandi/browser-inline-assets
Browse files Browse the repository at this point in the history
Enable inline view of assets
  • Loading branch information
waxlamp committed Mar 30, 2023
2 parents 3c8c0f4 + bb5f924 commit 83d3a10
Show file tree
Hide file tree
Showing 5 changed files with 76 additions and 7 deletions.
21 changes: 21 additions & 0 deletions dandiapi/api/storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,17 @@ def generate_presigned_download_url(self, key: str, path: str) -> str:
},
)

def generate_presigned_inline_url(self, key: str, path: str, content_type: str) -> str:
return self.connection.meta.client.generate_presigned_url(
'get_object',
Params={
'Bucket': self.bucket_name,
'Key': key,
'ResponseContentDisposition': f'inline; filename="{path}"',
'ResponseContentType': content_type,
},
)

def sha256_checksum(self, key: str) -> str:
calculator = ChecksumCalculatorFile()
obj = self.bucket.Object(key)
Expand Down Expand Up @@ -213,6 +224,16 @@ def generate_presigned_download_url(self, key: str, path: str) -> str:
response_headers={'response-content-disposition': f'attachment; filename="{path}"'},
)

def generate_presigned_inline_url(self, key: str, path: str, content_type: str) -> str:
return self.base_url_client.presigned_get_object(
self.bucket_name,
key,
response_headers={
'response-content-disposition': f'inline; filename="{path}"',
'response-content-type': content_type,
},
)

def sha256_checksum(self, key: str) -> str:
calculator = ChecksumCalculatorFile()
obj = self.client.get_object(self.bucket_name, key)
Expand Down
39 changes: 32 additions & 7 deletions dandiapi/api/views/asset.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
from django.conf import settings
from django.db import transaction
from django.db.models import QuerySet
from django.http import HttpResponseRedirect
from django.http import HttpResponse, HttpResponseRedirect
from django.utils.decorators import method_decorator
from django_filters import rest_framework as filters
from drf_yasg.utils import swagger_auto_schema
Expand All @@ -50,6 +50,7 @@
)
from dandiapi.api.views.serializers import (
AssetDetailSerializer,
AssetDownloadQueryParameterSerializer,
AssetListSerializer,
AssetPathsQueryParameterSerializer,
AssetPathsSerializer,
Expand Down Expand Up @@ -111,14 +112,14 @@ def retrieve(self, request, **kwargs):
method='GET',
operation_summary='Get the download link for an asset.',
operation_description='',
manual_parameters=[ASSET_ID_PARAM],
query_serializer=AssetDownloadQueryParameterSerializer,
responses={
200: None, # This disables the auto-generated 200 response
301: 'Redirect to object store',
},
)
@action(methods=['GET', 'HEAD'], detail=True)
def download(self, *args, **kwargs):
def download(self, request, *args, **kwargs):
asset = self.get_object()

# Raise error if zarr
Expand All @@ -137,11 +138,35 @@ def download(self, *args, **kwargs):

# Redirect to correct presigned URL
storage = asset_blob.blob.storage
return HttpResponseRedirect(
storage.generate_presigned_download_url(
asset_blob.blob.name, os.path.basename(asset.path)

serializer = AssetDownloadQueryParameterSerializer(data=request.query_params)
serializer.is_valid(raise_exception=True)
content_disposition = serializer.validated_data['content_disposition']
content_type = asset.metadata.get('encodingFormat', 'application/octet-stream')
asset_basename = os.path.basename(asset.path)

if content_disposition == 'attachment':
return HttpResponseRedirect(
storage.generate_presigned_download_url(asset_blob.blob.name, asset_basename)
)
)
elif content_disposition == 'inline':
url = storage.generate_presigned_inline_url(
asset_blob.blob.name,
asset_basename,
content_type,
)

if content_type == 'video/x-matroska':
return HttpResponse(
f"""
<video autoplay muted controls>
<source src="{url}" type="video/mp4">
</video>
""",
content_type='text/html',
)

return HttpResponseRedirect(url)

@swagger_auto_schema(
method='GET',
Expand Down
4 changes: 4 additions & 0 deletions dandiapi/api/views/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,10 @@ class Meta:
fields = ['status', 'validation_errors']


class AssetDownloadQueryParameterSerializer(serializers.Serializer):
content_disposition = serializers.ChoiceField(['attachment', 'inline'], default='attachment')


class EmbargoedSlugRelatedField(serializers.SlugRelatedField):
"""
A Field for cleanly serializing embargoed model fields.
Expand Down
3 changes: 3 additions & 0 deletions web/src/rest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,9 @@ const dandiRest = new Vue({
assetDownloadURI(identifier: string, version: string, uuid: string) {
return `${dandiApiRoot}assets/${uuid}/download/`;
},
assetInlineURI(identifier: string, version: string, uuid: string) {
return `${dandiApiRoot}assets/${uuid}/download?content_disposition=inline`;
},
assetMetadataURI(identifier: string, version: string, uuid: string) {
return `${dandiApiRoot}dandisets/${identifier}/versions/${version}/assets/${uuid}`;
},
Expand Down
16 changes: 16 additions & 0 deletions web/src/views/FileBrowserView/FileBrowser.vue
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,18 @@
</v-btn>
</v-list-item-action>

<v-list-item-action v-if="item.asset">
<v-btn
icon
:href="inlineURI(item.asset.asset_id)"
target="_blank"
>
<v-icon color="primary">
mdi-eye
</v-icon>
</v-btn>
</v-list-item-action>

<v-list-item-action v-if="item.asset">
<v-btn
icon
Expand Down Expand Up @@ -393,6 +405,10 @@ function downloadURI(asset_id: string) {
return dandiRest.assetDownloadURI(props.identifier, props.version, asset_id);
}
function inlineURI(asset_id: string) {
return dandiRest.assetInlineURI(props.identifier, props.version, asset_id);
}
function assetMetadataURI(asset_id: string) {
return dandiRest.assetMetadataURI(props.identifier, props.version, asset_id);
}
Expand Down

0 comments on commit 83d3a10

Please sign in to comment.