NODEDC_TASKMANAGER/plane-src/apps/api/plane/utils/attachment_preview.py

82 lines
2.8 KiB
Python

# Copyright (c) 2023-present Plane Software, Inc. and contributors
# SPDX-License-Identifier: AGPL-3.0-only
# See the LICENSE file for details.
from urllib.parse import quote
from botocore.exceptions import ClientError
from django.http import HttpResponse, StreamingHttpResponse
from django.utils import timezone
from plane.settings.storage import S3Storage
def _mark_preview_state(asset, status, error=None):
attributes = dict(asset.attributes or {})
if status == "failed":
attributes["preview_status"] = "failed"
attributes["preview_failed_at"] = timezone.now().isoformat()
if error:
attributes["preview_error"] = str(error)
else:
attributes.pop("preview_failed_at", None)
attributes.pop("preview_error", None)
attributes["preview_status"] = "ok"
asset.attributes = attributes
asset.save(update_fields=["attributes", "updated_at"])
def attachment_object_exists(asset):
storage = S3Storage(request=None)
try:
storage.s3_client.head_object(
Bucket=storage.aws_storage_bucket_name,
Key=str(asset.asset.name),
)
return True
except ClientError:
return False
def get_attachment_preview_response(request, asset, disposition="inline"):
storage = S3Storage(request=None)
range_header = request.META.get("HTTP_RANGE")
request_kwargs = {
"Bucket": storage.aws_storage_bucket_name,
"Key": str(asset.asset.name),
}
if range_header:
request_kwargs["Range"] = range_header
try:
storage_response = storage.s3_client.get_object(**request_kwargs)
if disposition == "inline" and asset.attributes.get("preview_status") == "failed":
_mark_preview_state(asset, "ok")
except ClientError as exc:
if disposition == "inline":
_mark_preview_state(asset, "failed", error=exc.response.get("Error", {}).get("Code", exc))
return HttpResponse("Attachment object not found.", status=404)
content_type = (
asset.attributes.get("type")
or storage_response.get("ContentType")
or "application/octet-stream"
)
filename = quote(asset.attributes.get("name") or "attachment")
response = StreamingHttpResponse(
storage_response["Body"].iter_chunks(chunk_size=8192),
status=206 if storage_response.get("ContentRange") else 200,
content_type=content_type,
)
response["Content-Disposition"] = f"{disposition}; filename*=UTF-8''{filename}"
response["Accept-Ranges"] = "bytes"
if storage_response.get("ContentLength") is not None:
response["Content-Length"] = storage_response["ContentLength"]
if storage_response.get("ContentRange"):
response["Content-Range"] = storage_response["ContentRange"]
return response