Skip to content

Commit a897d25

Browse files
committed
feat: implement file upload and download functionality in file manager
Signed-off-by: kang2453 <[email protected]>
1 parent 4fcfe15 commit a897d25

File tree

8 files changed

+308
-34
lines changed

8 files changed

+308
-34
lines changed

src/spaceone/file_manager/connector/aws_s3_connector.py

+22
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import logging
22
import boto3
3+
from io import BytesIO
4+
import botocore
35

46
from spaceone.core.error import *
57
from spaceone.file_manager.connector.file_base_connector import FileBaseConnector
@@ -72,3 +74,23 @@ def delete_file(self, file_id, file_name):
7274
@staticmethod
7375
def _generate_object_name(file_id, file_name):
7476
return f"{file_id}/{file_name}"
77+
78+
79+
def upload_file(self, remote_file_path:str, data: bytes) -> None:
80+
file_obj = BytesIO(data)
81+
if self.client is None:
82+
raise ERROR_CONNECTOR_CONFIGURATION(backend="AWSS3Connector")
83+
self.client.upload_fileobj(file_obj, self.bucket_name, remote_file_path)
84+
85+
def download_file(self, remote_file_path:str) :
86+
87+
if self.client is None:
88+
raise ERROR_CONNECTOR_CONFIGURATION(backend="AWSS3Connector")
89+
90+
# S3 객체 가져오기
91+
obj = self.client.get_object(Bucket=self.bucket_name, Key=remote_file_path)
92+
93+
# 파일 크기 출력
94+
# print(f"File size: {obj['ContentLength']} bytes")
95+
# S3 스트리밍 바디 얻기
96+
return obj["Body"]

src/spaceone/file_manager/connector/file_base_connector.py

+10
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44

55
class FileBaseConnector(BaseConnector):
6+
67
@abc.abstractmethod
78
def get_upload_url(self, file_id: str, file_name: str) -> [str, dict]:
89
pass
@@ -18,3 +19,12 @@ def check_file(self, file_id: str, file_name: str) -> bool:
1819
@abc.abstractmethod
1920
def delete_file(self, file_id: str, file_name: str) -> None:
2021
pass
22+
23+
@abc.abstractmethod
24+
def upload_file(self, remote_file_path: str, data:bytes) -> None:
25+
pass
26+
27+
@abc.abstractmethod
28+
def download_file(self, remote_file_path: str ):
29+
pass
30+
+10
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
from spaceone.core.error import *
22

33

4+
class ERROR_NOT_SUPPORTED_RESOURCE_GROUP(ERROR_BASE):
5+
_message = "Not supported resource group. (resource_group = {resource_group})"
6+
47
class ERROR_NOT_DEFINED_FILE_BACKEND(ERROR_BASE):
58
_message = "File backend not defined. (backend = {backend})"
9+
10+
class ERROR_FILE_DOWNLOAD_URL_EXIST(ERROR_BASE):
11+
_message = "File download url is not exist. (file_id = {file_id})"
12+
13+
14+
class ERROR_FILE_DOWNLOAD_FAILED(ERROR_BASE):
15+
_message = "File download failed. (file_id = {file_id})"
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,20 @@
1+
from curses import meta
12
import logging
3+
from turtle import down
24

35
from fastapi import Request, Depends, File, UploadFile
6+
from fastapi.responses import StreamingResponse
47
from fastapi.concurrency import run_in_threadpool
58
from fastapi_utils.cbv import cbv
69
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
710
from fastapi_utils.inferring_router import InferringRouter
11+
12+
13+
from spaceone.core import utils
814
from spaceone.core.fastapi.api import BaseAPI, exception_handler
915
from spaceone.file_manager.manager.file_connector_manager import FileConnectorManager
10-
1116
from spaceone.file_manager.service.file_service import FileService
17+
from spaceone.file_manager.error import *
1218

1319
_LOGGER = logging.getLogger(__name__)
1420
_AUTH_SCHEME = HTTPBearer(auto_error=False)
@@ -24,44 +30,184 @@ class Files(BaseAPI):
2430
@router.post("/public/upload")
2531
@exception_handler
2632
async def upload_public_file(self, request: Request, file: UploadFile = File(...)):
27-
metadata = {
33+
34+
params = {
2835
"token": self.token.credentials,
36+
"name": file.filename,
37+
"resource_group": "SYSTEM",
2938
}
39+
file_svc = FileService()
40+
response: dict = file_svc.add(params)
41+
42+
download_url = self.get_download_url(response)
43+
44+
# Update File
45+
file_conn_mgr = FileConnectorManager()
46+
# file_conn_mgr.upload_file(download_url, await file.read())
47+
await run_in_threadpool(file_conn_mgr.upload_file, download_url, await file.read())
48+
49+
response["download_url"] = download_url
50+
file_svc.update(response)
51+
return response
52+
53+
@router.get("/public/{file_id}")
54+
@exception_handler
55+
async def download_public_file(self, request: Request, file_id: str):
3056

3157
params = {
32-
"name": file.filename,
33-
"resource_group": "SYSTEM",
58+
"token": self.token.credentials,
59+
"file_id": file_id,
60+
"resource_group":"SYSTEM",
3461
}
3562

36-
file_svc = FileService(metadata)
37-
response: dict = await run_in_threadpool(file_svc.add, params)
63+
file_svc =FileService()
64+
file_vo: dict = file_svc.get(params)
3865

39-
file_id = response.get("file_id")
66+
download_url = file_vo["download_url"]
67+
if not download_url:
68+
raise ERROR_FILE_DOWNLOAD_URL_EXIST(file_id=file_id)
4069

4170
file_conn_mgr = FileConnectorManager()
71+
file_stream = await run_in_threadpool(file_conn_mgr.download_file, download_url)
72+
if not file_stream:
73+
raise ERROR_FILE_DOWNLOAD_FAILED(file_id=file_vo["file_id"])
74+
75+
return StreamingResponse(
76+
content=file_stream,
77+
media_type="application/octet-stream",
78+
headers={"Content-Disposition": f"attachment; filename={file_vo["name"]}"}
79+
)
80+
81+
@router.post("/domain/{domain_id}/upload")
82+
@exception_handler
83+
async def upload_domain_file(self, domain_id, request: Request, file: UploadFile = File(...)):
84+
85+
86+
params = {
87+
"token": self.token.credentials,
88+
"name": file.filename,
89+
"domain_id": domain_id,
90+
"resource_group": "DOMAIN",
91+
}
92+
file_svc = FileService()
93+
response: dict = file_svc.add(params)
94+
95+
download_url = self.get_download_url(response)
96+
4297
# Update File
43-
# file_id = file_vo.file_id
98+
file_conn_mgr = FileConnectorManager()
99+
await run_in_threadpool(file_conn_mgr.upload_file, download_url, await file.read())
44100

101+
response["download_url"] = download_url
102+
response = file_svc.update(response)
45103
return response
46104

47-
@router.get("/public/{file_id}")
105+
@router.get("/domain/{domain_id}/{file_id}")
48106
@exception_handler
49-
async def download_public_file(self, request: Request, file_id: str):
50-
metadata = {
107+
async def download_domain_file(self, domain_id:str, file_id:str, request: Request) -> StreamingResponse:
108+
109+
try:
110+
params = {
111+
"token": self.token.credentials,
112+
"file_id": file_id,
113+
"domain_id": domain_id,
114+
"resource_group":"DOMAIN",
115+
}
116+
117+
file_svc = FileService()
118+
file_vo: dict = file_svc.get(params)
119+
120+
download_url = file_vo["download_url"]
121+
if not download_url:
122+
raise ERROR_FILE_DOWNLOAD_URL_EXIST(file_id=file_id)
123+
124+
file_conn_mgr = FileConnectorManager()
125+
file_stream = await run_in_threadpool(file_conn_mgr.download_file, download_url)
126+
if not file_stream:
127+
raise ERROR_FILE_DOWNLOAD_FAILED(file_id=file_vo["file_id"])
128+
129+
return StreamingResponse(
130+
content=file_stream,
131+
media_type="binary/octet-stream",
132+
headers={"Content-Disposition": f"attachment; filename={file_vo["name"]}"}
133+
)
134+
except Exception as e:
135+
raise ERROR_FILE_DOWNLOAD_FAILED(file_id=file_id)
136+
137+
@router.post("/domain/{domain_id}/workspace/{workspace_id}/upload")
138+
@exception_handler
139+
async def upload_workspace_file(self, domain_id:str, workspace_id:str, request: Request, file: UploadFile = File(...)):
140+
141+
params = {
51142
"token": self.token.credentials,
143+
"name": file.filename,
144+
"domain_id": domain_id,
145+
"workspace_id": workspace_id,
146+
"resource_group": "WORKSPACE",
52147
}
148+
file_svc = FileService()
149+
response: dict = file_svc.add(params)
150+
151+
download_url = self.get_download_url(response)
152+
153+
# Update File
154+
file_conn_mgr = FileConnectorManager()
155+
await run_in_threadpool(file_conn_mgr.upload_file, download_url, await file.read())
156+
157+
response["download_url"] = download_url
158+
file_svc.update(response)
159+
return response
160+
161+
@router.get("/domain/{domain_id}/workspace/{workspace_id}/{file_id}")
162+
@exception_handler
163+
async def download_workspace_file(self, domain_id:str, workspace_id:str, file_id:str, request: Request):
53164

54165
params = {
166+
"token": self.token.credentials,
55167
"file_id": file_id,
168+
"domain_id": domain_id,
169+
"workspace_id": workspace_id,
170+
"resource_group": "WORKSPACE",
56171
}
57172

58-
file_svc = FileService(metadata)
59-
response: dict = await run_in_threadpool(file_svc.get, params)
60-
61-
file_id = response.get("file_id")
173+
file_svc =FileService()
174+
file_vo: dict = file_svc.get(params)
62175

176+
download_url = file_vo["download_url"]
177+
if not download_url:
178+
raise ERROR_FILE_DOWNLOAD_URL_EXIST(file_id=file_id)
179+
180+
63181
file_conn_mgr = FileConnectorManager()
64-
# Update File
65-
# file_id = file_vo.file_id
66-
67-
return response
182+
file_stream = await run_in_threadpool(file_conn_mgr.download_file, download_url)
183+
if not file_stream:
184+
raise ERROR_FILE_DOWNLOAD_FAILED(file_id=file_vo["file_id"])
185+
186+
return StreamingResponse(
187+
content=file_stream,
188+
media_type="binary/octet-stream",
189+
headers={"Content-Disposition": f"attachment; filename={file_vo["name"]}"}
190+
)
191+
192+
def get_download_url(self, response: dict ) -> str:
193+
194+
resource_group = response["resource_group"]
195+
file_id = response["file_id"]
196+
197+
if resource_group == "SYSTEM":
198+
download_url = "/files/public/" + file_id
199+
elif resource_group == "DOMAIN":
200+
domain_id = response["domain_id"]
201+
download_url = "/files/domain/" + domain_id + "/" + file_id
202+
elif resource_group == "WORKSPACE":
203+
domain_id = response["domain_id"]
204+
workspace_id = response["workspace_id"]
205+
download_url = "/files/domain/" + domain_id + "/workspace/" + workspace_id + "/" + file_id
206+
elif resource_group == "PROJECT":
207+
domain_id = response["domain_id"]
208+
user_id = response["user_id"]
209+
download_url = "/files/domain/" + domain_id + "/user/"+ user_id + "/" + file_id
210+
else:
211+
raise ERROR_NOT_SUPPORTED_RESOURCE_GROUP(resource_group=resource_group)
212+
213+
return download_url
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
2+
from curses import meta
3+
import logging
4+
from turtle import down
5+
6+
from fastapi import Request, Depends, File, UploadFile
7+
from fastapi.responses import StreamingResponse
8+
from fastapi.concurrency import run_in_threadpool
9+
from fastapi_utils.cbv import cbv
10+
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
11+
from fastapi_utils.inferring_router import InferringRouter
12+
13+
14+
from spaceone.core import utils
15+
from spaceone.core.fastapi.api import BaseAPI, exception_handler
16+
from spaceone.file_manager.manager.file_connector_manager import FileConnectorManager
17+
from spaceone.file_manager.service.file_service import FileService
18+
from spaceone.file_manager.error import *
19+
20+
_LOGGER = logging.getLogger(__name__)
21+
_AUTH_SCHEME = HTTPBearer(auto_error=False)
22+
23+
router = InferringRouter(include_in_schema=False)
24+
25+
# @cbv(router)
26+
# class Files(BaseAPI):
27+
# token: HTTPAuthorizationCredentials = Depends(_AUTH_SCHEME)
28+
# service = "file-manager"
29+
30+
# @router.post("/domain/{domain_id}/user/{user_id}/upload")
31+
# @exception_handler
32+
# async def upload_user_file(self, domain_id:str, user_id:str, request: Request, file: UploadFile = File(...)):
33+
34+
# params = {
35+
# "token": self.token.credentials,
36+
# "name": file.filename,
37+
# "domain_id": domain_id,
38+
# "user_id": user_id,
39+
# "resource_group": "PROJECT",
40+
# }
41+
# file_svc = FileService()
42+
# response: dict = file_svc.add(params)
43+
44+
# download_url = self.get_download_url(response)
45+
46+
# # Update File
47+
# file_conn_mgr = FileConnectorManager()
48+
# await run_in_threadpool(file_conn_mgr.upload_file, download_url, await file.read())
49+
50+
# response["download_url"] = download_url
51+
# file_svc.update(response)
52+
# return response
53+
54+
# @router.get("/domain/{domain_id}/user/{user_id}/{file_id}")
55+
# @exception_handler
56+
# async def download_user_file(self, domain_id:str, user_id:str, file_id:str, request: Request):
57+
58+
# params = {
59+
# "token": self.token.credentials,
60+
# "file_id": file_id,
61+
# "domain_id": domain_id,
62+
# "user_id": user_id,
63+
# "resource_group": "PROJECT",
64+
# }
65+
66+
# file_svc =FileService()
67+
# file_vo: dict = file_svc.get(params)
68+
69+
# download_url = file_vo["download_url"]
70+
# if not download_url:
71+
# raise ERROR_FILE_DOWNLOAD_URL_EXIST(file_id=file_id)
72+
73+
# file_conn_mgr = FileConnectorManager()
74+
# file_stream = await run_in_threadpool(file_conn_mgr.download_file, download_url)
75+
# if not file_stream:
76+
# raise ERROR_FILE_DOWNLOAD_FAILED(file_id=file_vo["file_id"])
77+
78+
# return StreamingResponse(
79+
# content=file_stream,
80+
# media_type="binary/octet-stream",
81+
# headers={"Content-Disposition": f"attachment; filename={file_vo["name"]}"}
82+
# )

src/spaceone/file_manager/manager/file_connector_manager.py

+6
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,9 @@ def check_file(self, file_id: str, file_name: str):
3636

3737
def delete_file(self, file_id: str, file_name: str) -> None:
3838
self.file_conn.delete_file(file_id, file_name)
39+
40+
def upload_file(self, remote_file_path:str, file_binary: bytes) -> None:
41+
self.file_conn.upload_file(remote_file_path, file_binary)
42+
43+
def download_file(self, remote_file_path:str) :
44+
return self.file_conn.download_file(remote_file_path)

0 commit comments

Comments
 (0)