Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add object #26

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 96 additions & 1 deletion cloud_storage_handler/api/elixircloud/csh/controllers.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,110 @@
"""ELIXIR's Cloud Storage Handler controllers."""

import logging
import os
import uuid
from http import HTTPStatus

from flask import jsonify
from flask import current_app, jsonify, request
from minio.error import S3Error

logger = logging.getLogger(__name__)

CHUNK_SIZE = 5 * 1024 * 1024


def home():
"""Endpoint to return a welcome message."""
return jsonify(
{"message": "Welcome to the Cloud Storage Handler server!"}
), HTTPStatus.OK


def get_chunks(file_obj, chunk_size):
"""Generate chunks from a file object."""
while True:
chunk = file_obj.read(chunk_size)
if not chunk:
break
yield chunk


def upload_object():
psankhe28 marked this conversation as resolved.
Show resolved Hide resolved
"""Handles file uploads to cloud storage.

Retrieves files from the request, processes each file into chunks,
and uploads them to the specified storage bucket. Returns a response
with a success message and details about the uploaded file(s).
"""
files = request.files.getlist("files")
responses = []
minio_config = current_app.config.foca.custom.minio
bucket_name = minio_config.bucket_name
minio_client = current_app.config.foca.custom.minio.client.client
upload_dir = "/tmp/upload"

os.makedirs(upload_dir, exist_ok=True)

for file in files:
if not file:
return jsonify({"error": "No file provided"}), HTTPStatus.BAD_REQUEST

file_id = str(uuid.uuid4())
file_path = os.path.join(upload_dir, f"{file_id}.temp")

with open(file_path, "wb") as f:
file.save(f)

total_size = os.path.getsize(file_path)
total_chunks = (total_size // CHUNK_SIZE) + (
1 if total_size % CHUNK_SIZE > 0 else 0
)

try:
# Stream the file to disk in chunks for better performance with large files
with open(file_path, "wb") as dest:
for chunk in file.stream:
dest.write(chunk)

total_size = os.path.getsize(file_path)
total_chunks = (total_size // CHUNK_SIZE) + (
1 if total_size % CHUNK_SIZE > 0 else 0
)

# Stream each chunk to Minio for upload
with open(file_path, "rb") as f:
for i, chunk in enumerate(get_chunks(f, CHUNK_SIZE)):
try:
minio_client.put_object(
bucket_name,
f"{file_id}/chunk_{i}",
chunk,
len(chunk),
metadata={"description": "Chunk upload via Flask"},
)
except S3Error as e:
logger.error(
f"Failed to upload chunk {i} for file {file_id}: {str(e)}"
)
return jsonify(
{"error": "Failed to upload file to cloud storage"}
), HTTPStatus.INTERNAL_SERVER_ERROR
except Exception as e:
logger.error(f"Error processing file {file.filename}: {str(e)}")
return jsonify(
{"error": "An error occurred while processing the file"}
), HTTPStatus.INTERNAL_SERVER_ERROR
finally:
# Cleanup temporary file
if os.path.exists(file_path):
os.remove(file_path)

responses.append(
{
"message": "File uploaded successfully",
"file_id": file_id,
"total_chunks": total_chunks,
}
)

return jsonify(responses), HTTPStatus.OK
32 changes: 32 additions & 0 deletions cloud_storage_handler/api/specs/specs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,36 @@
description: The request is malformed.
'500':
description: An unexpected error occurred.
/object:
post:
description: |
Create a new object upload using the TUS protocol. The request creates an empty object and returns an upload URL.

Check failure on line 41 in cloud_storage_handler/api/specs/specs.yaml

View workflow job for this annotation

GitHub Actions / Pre-commit checks

41:81 [line-length] line too long (121 > 80 characters)
operationId: upload_object
requestBody:
required: true
content:
multipart/form-data:
schema:
type: object
properties:
file:
type: string
format: binary
description: The file to be uploaded (supports .bib, .txt, .docx, and other file types).

Check failure on line 53 in cloud_storage_handler/api/specs/specs.yaml

View workflow job for this annotation

GitHub Actions / Pre-commit checks

53:81 [line-length] line too long (106 > 80 characters)
responses:
'201':
description: Object upload created successfully.
headers:
Location:
schema:
type: string
description: URL to upload object chunks using PATCH requests.
Tus-Resumable:
schema:
type: string
description: Version of the TUS protocol used.
'400':
description: Bad request or malformed request body.
'500':
description: An unexpected error occurred.
...
Loading
Loading