Skip to content

feat: implement file upload and download functionality in file manager #12

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

Merged
merged 1 commit into from
Dec 31, 2024
Merged
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
22 changes: 22 additions & 0 deletions src/spaceone/file_manager/connector/aws_s3_connector.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import logging
import boto3
from io import BytesIO
import botocore

from spaceone.core.error import *
from spaceone.file_manager.connector.file_base_connector import FileBaseConnector
Expand Down Expand Up @@ -72,3 +74,23 @@ def delete_file(self, file_id, file_name):
@staticmethod
def _generate_object_name(file_id, file_name):
return f"{file_id}/{file_name}"


def upload_file(self, remote_file_path:str, data: bytes) -> None:
file_obj = BytesIO(data)
if self.client is None:
raise ERROR_CONNECTOR_CONFIGURATION(backend="AWSS3Connector")
self.client.upload_fileobj(file_obj, self.bucket_name, remote_file_path)

def download_file(self, remote_file_path:str) :

if self.client is None:
raise ERROR_CONNECTOR_CONFIGURATION(backend="AWSS3Connector")

# S3 객체 가져오기
obj = self.client.get_object(Bucket=self.bucket_name, Key=remote_file_path)

# 파일 크기 출력
# print(f"File size: {obj['ContentLength']} bytes")
# S3 스트리밍 바디 얻기
return obj["Body"]
10 changes: 10 additions & 0 deletions src/spaceone/file_manager/connector/file_base_connector.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@


class FileBaseConnector(BaseConnector):

@abc.abstractmethod
def get_upload_url(self, file_id: str, file_name: str) -> [str, dict]:
pass
Expand All @@ -18,3 +19,12 @@ def check_file(self, file_id: str, file_name: str) -> bool:
@abc.abstractmethod
def delete_file(self, file_id: str, file_name: str) -> None:
pass

@abc.abstractmethod
def upload_file(self, remote_file_path: str, data:bytes) -> None:
pass

@abc.abstractmethod
def download_file(self, remote_file_path: str ):
pass

10 changes: 10 additions & 0 deletions src/spaceone/file_manager/error/custom.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
from spaceone.core.error import *


class ERROR_NOT_SUPPORTED_RESOURCE_GROUP(ERROR_BASE):
_message = "Not supported resource group. (resource_group = {resource_group})"

class ERROR_NOT_DEFINED_FILE_BACKEND(ERROR_BASE):
_message = "File backend not defined. (backend = {backend})"

class ERROR_FILE_DOWNLOAD_URL_EXIST(ERROR_BASE):
_message = "File download url is not exist. (file_id = {file_id})"


class ERROR_FILE_DOWNLOAD_FAILED(ERROR_BASE):
_message = "File download failed. (file_id = {file_id})"
184 changes: 165 additions & 19 deletions src/spaceone/file_manager/interface/rest/files.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
from curses import meta
import logging
from turtle import down

from fastapi import Request, Depends, File, UploadFile
from fastapi.responses import StreamingResponse
from fastapi.concurrency import run_in_threadpool
from fastapi_utils.cbv import cbv
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from fastapi_utils.inferring_router import InferringRouter


from spaceone.core import utils
from spaceone.core.fastapi.api import BaseAPI, exception_handler
from spaceone.file_manager.manager.file_connector_manager import FileConnectorManager

from spaceone.file_manager.service.file_service import FileService
from spaceone.file_manager.error import *

_LOGGER = logging.getLogger(__name__)
_AUTH_SCHEME = HTTPBearer(auto_error=False)
Expand All @@ -24,44 +30,184 @@ class Files(BaseAPI):
@router.post("/public/upload")
@exception_handler
async def upload_public_file(self, request: Request, file: UploadFile = File(...)):
metadata = {

params = {
"token": self.token.credentials,
"name": file.filename,
"resource_group": "SYSTEM",
}
file_svc = FileService()
response: dict = file_svc.add(params)

download_url = self.get_download_url(response)

# Update File
file_conn_mgr = FileConnectorManager()
# file_conn_mgr.upload_file(download_url, await file.read())
await run_in_threadpool(file_conn_mgr.upload_file, download_url, await file.read())

response["download_url"] = download_url
file_svc.update(response)
return response

@router.get("/public/{file_id}")
@exception_handler
async def download_public_file(self, request: Request, file_id: str):

params = {
"name": file.filename,
"resource_group": "SYSTEM",
"token": self.token.credentials,
"file_id": file_id,
"resource_group":"SYSTEM",
}

file_svc = FileService(metadata)
response: dict = await run_in_threadpool(file_svc.add, params)
file_svc =FileService()
file_vo: dict = file_svc.get(params)

file_id = response.get("file_id")
download_url = file_vo["download_url"]
if not download_url:
raise ERROR_FILE_DOWNLOAD_URL_EXIST(file_id=file_id)

file_conn_mgr = FileConnectorManager()
file_stream = await run_in_threadpool(file_conn_mgr.download_file, download_url)
if not file_stream:
raise ERROR_FILE_DOWNLOAD_FAILED(file_id=file_vo["file_id"])

return StreamingResponse(
content=file_stream,
media_type="application/octet-stream",
headers={"Content-Disposition": f"attachment; filename={file_vo["name"]}"}
)

@router.post("/domain/{domain_id}/upload")
@exception_handler
async def upload_domain_file(self, domain_id, request: Request, file: UploadFile = File(...)):


params = {
"token": self.token.credentials,
"name": file.filename,
"domain_id": domain_id,
"resource_group": "DOMAIN",
}
file_svc = FileService()
response: dict = file_svc.add(params)

download_url = self.get_download_url(response)

# Update File
# file_id = file_vo.file_id
file_conn_mgr = FileConnectorManager()
await run_in_threadpool(file_conn_mgr.upload_file, download_url, await file.read())

response["download_url"] = download_url
response = file_svc.update(response)
return response

@router.get("/public/{file_id}")
@router.get("/domain/{domain_id}/{file_id}")
@exception_handler
async def download_public_file(self, request: Request, file_id: str):
metadata = {
async def download_domain_file(self, domain_id:str, file_id:str, request: Request) -> StreamingResponse:

try:
params = {
"token": self.token.credentials,
"file_id": file_id,
"domain_id": domain_id,
"resource_group":"DOMAIN",
}

file_svc = FileService()
file_vo: dict = file_svc.get(params)

download_url = file_vo["download_url"]
if not download_url:
raise ERROR_FILE_DOWNLOAD_URL_EXIST(file_id=file_id)

file_conn_mgr = FileConnectorManager()
file_stream = await run_in_threadpool(file_conn_mgr.download_file, download_url)
if not file_stream:
raise ERROR_FILE_DOWNLOAD_FAILED(file_id=file_vo["file_id"])

return StreamingResponse(
content=file_stream,
media_type="binary/octet-stream",
headers={"Content-Disposition": f"attachment; filename={file_vo["name"]}"}
)
except Exception as e:
raise ERROR_FILE_DOWNLOAD_FAILED(file_id=file_id)

@router.post("/domain/{domain_id}/workspace/{workspace_id}/upload")
@exception_handler
async def upload_workspace_file(self, domain_id:str, workspace_id:str, request: Request, file: UploadFile = File(...)):

params = {
"token": self.token.credentials,
"name": file.filename,
"domain_id": domain_id,
"workspace_id": workspace_id,
"resource_group": "WORKSPACE",
}
file_svc = FileService()
response: dict = file_svc.add(params)

download_url = self.get_download_url(response)

# Update File
file_conn_mgr = FileConnectorManager()
await run_in_threadpool(file_conn_mgr.upload_file, download_url, await file.read())

response["download_url"] = download_url
file_svc.update(response)
return response

@router.get("/domain/{domain_id}/workspace/{workspace_id}/{file_id}")
@exception_handler
async def download_workspace_file(self, domain_id:str, workspace_id:str, file_id:str, request: Request):

params = {
"token": self.token.credentials,
"file_id": file_id,
"domain_id": domain_id,
"workspace_id": workspace_id,
"resource_group": "WORKSPACE",
}

file_svc = FileService(metadata)
response: dict = await run_in_threadpool(file_svc.get, params)

file_id = response.get("file_id")
file_svc =FileService()
file_vo: dict = file_svc.get(params)

download_url = file_vo["download_url"]
if not download_url:
raise ERROR_FILE_DOWNLOAD_URL_EXIST(file_id=file_id)


file_conn_mgr = FileConnectorManager()
# Update File
# file_id = file_vo.file_id

return response
file_stream = await run_in_threadpool(file_conn_mgr.download_file, download_url)
if not file_stream:
raise ERROR_FILE_DOWNLOAD_FAILED(file_id=file_vo["file_id"])

return StreamingResponse(
content=file_stream,
media_type="binary/octet-stream",
headers={"Content-Disposition": f"attachment; filename={file_vo["name"]}"}
)

def get_download_url(self, response: dict ) -> str:

resource_group = response["resource_group"]
file_id = response["file_id"]

if resource_group == "SYSTEM":
download_url = "/files/public/" + file_id
elif resource_group == "DOMAIN":
domain_id = response["domain_id"]
download_url = "/files/domain/" + domain_id + "/" + file_id
elif resource_group == "WORKSPACE":
domain_id = response["domain_id"]
workspace_id = response["workspace_id"]
download_url = "/files/domain/" + domain_id + "/workspace/" + workspace_id + "/" + file_id
elif resource_group == "PROJECT":
domain_id = response["domain_id"]
user_id = response["user_id"]
download_url = "/files/domain/" + domain_id + "/user/"+ user_id + "/" + file_id
else:
raise ERROR_NOT_SUPPORTED_RESOURCE_GROUP(resource_group=resource_group)

return download_url
82 changes: 82 additions & 0 deletions src/spaceone/file_manager/interface/rest/users.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@

from curses import meta
import logging
from turtle import down

from fastapi import Request, Depends, File, UploadFile
from fastapi.responses import StreamingResponse
from fastapi.concurrency import run_in_threadpool
from fastapi_utils.cbv import cbv
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from fastapi_utils.inferring_router import InferringRouter


from spaceone.core import utils
from spaceone.core.fastapi.api import BaseAPI, exception_handler
from spaceone.file_manager.manager.file_connector_manager import FileConnectorManager
from spaceone.file_manager.service.file_service import FileService
from spaceone.file_manager.error import *

_LOGGER = logging.getLogger(__name__)
_AUTH_SCHEME = HTTPBearer(auto_error=False)

router = InferringRouter(include_in_schema=False)

# @cbv(router)
# class Files(BaseAPI):
# token: HTTPAuthorizationCredentials = Depends(_AUTH_SCHEME)
# service = "file-manager"

# @router.post("/domain/{domain_id}/user/{user_id}/upload")
# @exception_handler
# async def upload_user_file(self, domain_id:str, user_id:str, request: Request, file: UploadFile = File(...)):

# params = {
# "token": self.token.credentials,
# "name": file.filename,
# "domain_id": domain_id,
# "user_id": user_id,
# "resource_group": "PROJECT",
# }
# file_svc = FileService()
# response: dict = file_svc.add(params)

# download_url = self.get_download_url(response)

# # Update File
# file_conn_mgr = FileConnectorManager()
# await run_in_threadpool(file_conn_mgr.upload_file, download_url, await file.read())

# response["download_url"] = download_url
# file_svc.update(response)
# return response

# @router.get("/domain/{domain_id}/user/{user_id}/{file_id}")
# @exception_handler
# async def download_user_file(self, domain_id:str, user_id:str, file_id:str, request: Request):

# params = {
# "token": self.token.credentials,
# "file_id": file_id,
# "domain_id": domain_id,
# "user_id": user_id,
# "resource_group": "PROJECT",
# }

# file_svc =FileService()
# file_vo: dict = file_svc.get(params)

# download_url = file_vo["download_url"]
# if not download_url:
# raise ERROR_FILE_DOWNLOAD_URL_EXIST(file_id=file_id)

# file_conn_mgr = FileConnectorManager()
# file_stream = await run_in_threadpool(file_conn_mgr.download_file, download_url)
# if not file_stream:
# raise ERROR_FILE_DOWNLOAD_FAILED(file_id=file_vo["file_id"])

# return StreamingResponse(
# content=file_stream,
# media_type="binary/octet-stream",
# headers={"Content-Disposition": f"attachment; filename={file_vo["name"]}"}
# )
6 changes: 6 additions & 0 deletions src/spaceone/file_manager/manager/file_connector_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,9 @@ def check_file(self, file_id: str, file_name: str):

def delete_file(self, file_id: str, file_name: str) -> None:
self.file_conn.delete_file(file_id, file_name)

def upload_file(self, remote_file_path:str, file_binary: bytes) -> None:
self.file_conn.upload_file(remote_file_path, file_binary)

def download_file(self, remote_file_path:str) :
return self.file_conn.download_file(remote_file_path)
Loading
Loading