|
|
|
|
@ -3,7 +3,6 @@
|
|
|
|
|
#
|
|
|
|
|
# SPDX-License-Identifier: MIT
|
|
|
|
|
|
|
|
|
|
import errno
|
|
|
|
|
import io
|
|
|
|
|
import os
|
|
|
|
|
import os.path as osp
|
|
|
|
|
@ -37,19 +36,15 @@ from drf_spectacular.plumbing import build_array_type, build_basic_type
|
|
|
|
|
|
|
|
|
|
from rest_framework import mixins, serializers, status, viewsets
|
|
|
|
|
from rest_framework.decorators import action
|
|
|
|
|
from rest_framework.exceptions import APIException, NotFound, ValidationError
|
|
|
|
|
from rest_framework.exceptions import APIException, NotFound, ValidationError, PermissionDenied
|
|
|
|
|
from rest_framework.permissions import SAFE_METHODS
|
|
|
|
|
from rest_framework.renderers import JSONRenderer
|
|
|
|
|
from rest_framework.response import Response
|
|
|
|
|
from rest_framework.exceptions import PermissionDenied
|
|
|
|
|
from django_sendfile import sendfile
|
|
|
|
|
|
|
|
|
|
import cvat.apps.dataset_manager as dm
|
|
|
|
|
import cvat.apps.dataset_manager.views # pylint: disable=unused-import
|
|
|
|
|
from cvat.apps.engine.cloud_provider import (
|
|
|
|
|
db_storage_to_storage_instance, import_from_cloud_storage, export_to_cloud_storage,
|
|
|
|
|
Status as CloudStorageStatus
|
|
|
|
|
)
|
|
|
|
|
from cvat.apps.engine.cloud_provider import db_storage_to_storage_instance
|
|
|
|
|
from cvat.apps.dataset_manager.bindings import CvatImportError
|
|
|
|
|
from cvat.apps.dataset_manager.serializers import DatasetFormatsSerializer
|
|
|
|
|
from cvat.apps.engine.frame_provider import FrameProvider
|
|
|
|
|
@ -73,7 +68,9 @@ from cvat.apps.engine.serializers import (
|
|
|
|
|
ProjectFileSerializer, TaskFileSerializer)
|
|
|
|
|
|
|
|
|
|
from utils.dataset_manifest import ImageManifestManager
|
|
|
|
|
from cvat.apps.engine.utils import av_scan_paths, process_failed_job, configure_dependent_job
|
|
|
|
|
from cvat.apps.engine.utils import (
|
|
|
|
|
av_scan_paths, process_failed_job, configure_dependent_job, parse_exception_message
|
|
|
|
|
)
|
|
|
|
|
from cvat.apps.engine import backup
|
|
|
|
|
from cvat.apps.engine.mixins import PartialUpdateModelMixin, UploadMixin, AnnotationMixin, SerializeMixin, DestroyModelMixin, CreateModelMixin
|
|
|
|
|
from cvat.apps.engine.location import get_location_configuration, StorageType
|
|
|
|
|
@ -674,6 +671,7 @@ class DataChunkGetter:
|
|
|
|
|
|
|
|
|
|
frame_provider = FrameProvider(db_data, self.dimension)
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
if self.type == 'chunk':
|
|
|
|
|
start_chunk = frame_provider.get_chunk_number(start)
|
|
|
|
|
stop_chunk = frame_provider.get_chunk_number(stop)
|
|
|
|
|
@ -721,6 +719,10 @@ class DataChunkGetter:
|
|
|
|
|
else:
|
|
|
|
|
return Response(data='unknown data type {}.'.format(self.type),
|
|
|
|
|
status=status.HTTP_400_BAD_REQUEST)
|
|
|
|
|
except (ValidationError, PermissionDenied, NotFound) as ex:
|
|
|
|
|
msg = str(ex) if not isinstance(ex, ValidationError) else \
|
|
|
|
|
'\n'.join([str(d) for d in ex.detail])
|
|
|
|
|
return Response(data=msg, status=ex.status_code)
|
|
|
|
|
|
|
|
|
|
@extend_schema(tags=['tasks'])
|
|
|
|
|
@extend_schema_view(
|
|
|
|
|
@ -1225,7 +1227,7 @@ class TaskViewSet(viewsets.GenericViewSet, mixins.ListModelMixin,
|
|
|
|
|
# It's not really clear how it is possible, but it can
|
|
|
|
|
# lead to an error in serializing the response
|
|
|
|
|
# https://github.com/opencv/cvat/issues/5215
|
|
|
|
|
response = { "state": "Failed", "message": job.exc_info or "Unknown error" }
|
|
|
|
|
response = { "state": "Failed", "message": parse_exception_message(job.exc_info or "Unknown error") }
|
|
|
|
|
else:
|
|
|
|
|
response = { "state": "Started" }
|
|
|
|
|
if job.meta.get('status'):
|
|
|
|
|
@ -2068,16 +2070,9 @@ class CloudStorageViewSet(viewsets.GenericViewSet, mixins.ListModelMixin,
|
|
|
|
|
db_storage = self.get_object()
|
|
|
|
|
storage = db_storage_to_storage_instance(db_storage)
|
|
|
|
|
if not db_storage.manifests.count():
|
|
|
|
|
raise Exception('There is no manifest file')
|
|
|
|
|
raise ValidationError('There is no manifest file')
|
|
|
|
|
manifest_path = request.query_params.get('manifest_path', db_storage.manifests.first().filename)
|
|
|
|
|
manifest_prefix = os.path.dirname(manifest_path)
|
|
|
|
|
file_status = storage.get_file_status(manifest_path)
|
|
|
|
|
if file_status == CloudStorageStatus.NOT_FOUND:
|
|
|
|
|
raise FileNotFoundError(errno.ENOENT,
|
|
|
|
|
"Not found on the cloud storage {}".format(db_storage.display_name), manifest_path)
|
|
|
|
|
elif file_status == CloudStorageStatus.FORBIDDEN:
|
|
|
|
|
raise PermissionError(errno.EACCES,
|
|
|
|
|
"Access to the file on the '{}' cloud storage is denied".format(db_storage.display_name), manifest_path)
|
|
|
|
|
|
|
|
|
|
full_manifest_path = os.path.join(db_storage.get_storage_dirname(), manifest_path)
|
|
|
|
|
if not os.path.exists(full_manifest_path) or \
|
|
|
|
|
@ -2093,20 +2088,15 @@ class CloudStorageViewSet(viewsets.GenericViewSet, mixins.ListModelMixin,
|
|
|
|
|
message = f"Storage {pk} does not exist"
|
|
|
|
|
slogger.glob.error(message)
|
|
|
|
|
return HttpResponseNotFound(message)
|
|
|
|
|
except FileNotFoundError as ex:
|
|
|
|
|
msg = f"{ex.strerror} {ex.filename}"
|
|
|
|
|
except (ValidationError, PermissionDenied, NotFound) as ex:
|
|
|
|
|
msg = str(ex) if not isinstance(ex, ValidationError) else \
|
|
|
|
|
'\n'.join([str(d) for d in ex.detail])
|
|
|
|
|
slogger.cloud_storage[pk].info(msg)
|
|
|
|
|
return Response(data=msg, status=status.HTTP_404_NOT_FOUND)
|
|
|
|
|
return Response(data=msg, status=ex.status_code)
|
|
|
|
|
except Exception as ex:
|
|
|
|
|
# check that cloud storage was not deleted
|
|
|
|
|
storage_status = storage.get_status() if storage else None
|
|
|
|
|
if storage_status == CloudStorageStatus.FORBIDDEN:
|
|
|
|
|
msg = 'The resource {} is no longer available. Access forbidden.'.format(storage.name)
|
|
|
|
|
elif storage_status == CloudStorageStatus.NOT_FOUND:
|
|
|
|
|
msg = 'The resource {} not found. It may have been deleted.'.format(storage.name)
|
|
|
|
|
else:
|
|
|
|
|
msg = str(ex)
|
|
|
|
|
return HttpResponseBadRequest(msg)
|
|
|
|
|
slogger.glob.error(str(ex))
|
|
|
|
|
return Response("An internal error has occurred",
|
|
|
|
|
status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
|
|
|
|
|
|
|
|
|
@extend_schema(summary='Method returns a preview image from a cloud storage',
|
|
|
|
|
responses={
|
|
|
|
|
@ -2120,7 +2110,7 @@ class CloudStorageViewSet(viewsets.GenericViewSet, mixins.ListModelMixin,
|
|
|
|
|
if not os.path.exists(db_storage.get_preview_path()):
|
|
|
|
|
storage = db_storage_to_storage_instance(db_storage)
|
|
|
|
|
if not db_storage.manifests.count():
|
|
|
|
|
raise Exception('Cannot get the cloud storage preview. There is no manifest file')
|
|
|
|
|
raise ValidationError('Cannot get the cloud storage preview. There is no manifest file')
|
|
|
|
|
preview_path = None
|
|
|
|
|
for manifest_model in db_storage.manifests.all():
|
|
|
|
|
manifest_prefix = os.path.dirname(manifest_model.filename)
|
|
|
|
|
@ -2145,13 +2135,6 @@ class CloudStorageViewSet(viewsets.GenericViewSet, mixins.ListModelMixin,
|
|
|
|
|
slogger.cloud_storage[pk].info(msg)
|
|
|
|
|
return HttpResponseBadRequest(msg)
|
|
|
|
|
|
|
|
|
|
file_status = storage.get_file_status(preview_path)
|
|
|
|
|
if file_status == CloudStorageStatus.NOT_FOUND:
|
|
|
|
|
raise FileNotFoundError(errno.ENOENT,
|
|
|
|
|
"Not found on the cloud storage {}".format(db_storage.display_name), preview_path)
|
|
|
|
|
elif file_status == CloudStorageStatus.FORBIDDEN:
|
|
|
|
|
raise PermissionError(errno.EACCES,
|
|
|
|
|
"Access to the file on the '{}' cloud storage is denied".format(db_storage.display_name), preview_path)
|
|
|
|
|
with NamedTemporaryFile() as temp_image:
|
|
|
|
|
storage.download_file(preview_path, temp_image.name)
|
|
|
|
|
reader = ImageListReader([temp_image.name])
|
|
|
|
|
@ -2163,18 +2146,15 @@ class CloudStorageViewSet(viewsets.GenericViewSet, mixins.ListModelMixin,
|
|
|
|
|
message = f"Storage {pk} does not exist"
|
|
|
|
|
slogger.glob.error(message)
|
|
|
|
|
return HttpResponseNotFound(message)
|
|
|
|
|
except PermissionDenied:
|
|
|
|
|
raise
|
|
|
|
|
except (ValidationError, PermissionDenied, NotFound) as ex:
|
|
|
|
|
msg = str(ex) if not isinstance(ex, ValidationError) else \
|
|
|
|
|
'\n'.join([str(d) for d in ex.detail])
|
|
|
|
|
slogger.cloud_storage[pk].info(msg)
|
|
|
|
|
return Response(data=msg, status=ex.status_code)
|
|
|
|
|
except Exception as ex:
|
|
|
|
|
# check that cloud storage was not deleted
|
|
|
|
|
storage_status = storage.get_status() if storage else None
|
|
|
|
|
if storage_status == CloudStorageStatus.FORBIDDEN:
|
|
|
|
|
msg = 'The resource {} is no longer available. Access forbidden.'.format(storage.name)
|
|
|
|
|
elif storage_status == CloudStorageStatus.NOT_FOUND:
|
|
|
|
|
msg = 'The resource {} not found. It may have been deleted.'.format(storage.name)
|
|
|
|
|
else:
|
|
|
|
|
msg = str(ex)
|
|
|
|
|
return HttpResponseBadRequest(msg)
|
|
|
|
|
slogger.glob.error(str(ex))
|
|
|
|
|
return Response("An internal error has occurred",
|
|
|
|
|
status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
|
|
|
|
|
|
|
|
|
@extend_schema(summary='Method returns a cloud storage status',
|
|
|
|
|
responses={
|
|
|
|
|
@ -2186,7 +2166,7 @@ class CloudStorageViewSet(viewsets.GenericViewSet, mixins.ListModelMixin,
|
|
|
|
|
db_storage = self.get_object()
|
|
|
|
|
storage = db_storage_to_storage_instance(db_storage)
|
|
|
|
|
storage_status = storage.get_status()
|
|
|
|
|
return HttpResponse(storage_status)
|
|
|
|
|
return Response(storage_status)
|
|
|
|
|
except CloudStorageModel.DoesNotExist:
|
|
|
|
|
message = f"Storage {pk} does not exist"
|
|
|
|
|
slogger.glob.error(message)
|
|
|
|
|
@ -2229,7 +2209,7 @@ def rq_handler(job, exc_type, exc_value, tb):
|
|
|
|
|
def _download_file_from_bucket(db_storage, filename, key):
|
|
|
|
|
storage = db_storage_to_storage_instance(db_storage)
|
|
|
|
|
|
|
|
|
|
data = import_from_cloud_storage(storage, key)
|
|
|
|
|
data = storage.download_fileobj(key)
|
|
|
|
|
with open(filename, 'wb+') as f:
|
|
|
|
|
f.write(data.getbuffer())
|
|
|
|
|
|
|
|
|
|
@ -2367,7 +2347,12 @@ def _export_annotations(db_instance, rq_id, request, format_name, action, callba
|
|
|
|
|
db_storage = get_object_or_404(CloudStorageModel, pk=storage_id)
|
|
|
|
|
storage = db_storage_to_storage_instance(db_storage)
|
|
|
|
|
|
|
|
|
|
export_to_cloud_storage(storage, file_path, filename)
|
|
|
|
|
try:
|
|
|
|
|
storage.upload_file(file_path, filename)
|
|
|
|
|
except (ValidationError, PermissionDenied, NotFound) as ex:
|
|
|
|
|
msg = str(ex) if not isinstance(ex, ValidationError) else \
|
|
|
|
|
'\n'.join([str(d) for d in ex.detail])
|
|
|
|
|
return Response(data=msg, status=ex.status_code)
|
|
|
|
|
return Response(status=status.HTTP_200_OK)
|
|
|
|
|
else:
|
|
|
|
|
raise NotImplementedError()
|
|
|
|
|
|