82 lines
2.8 KiB
Python
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
|