fix(file and app):
1. Handle the encoding issue when downloading Markdown files; 2. Experience the sharing of memory configuration
This commit is contained in:
@@ -574,8 +574,12 @@ async def get_file_url(
|
|||||||
# For local storage, generate signed URL with expiration
|
# For local storage, generate signed URL with expiration
|
||||||
url = generate_signed_url(str(file_id), expires)
|
url = generate_signed_url(str(file_id), expires)
|
||||||
else:
|
else:
|
||||||
# For remote storage (OSS/S3), get presigned URL
|
# For remote storage (OSS/S3), get presigned URL with forced download
|
||||||
url = await storage_service.get_file_url(file_key, expires=expires)
|
url = await storage_service.get_file_url(
|
||||||
|
file_key,
|
||||||
|
expires=expires,
|
||||||
|
file_name=file_metadata.file_name,
|
||||||
|
)
|
||||||
url = _match_scheme(request, url)
|
url = _match_scheme(request, url)
|
||||||
|
|
||||||
api_logger.info(f"Generated file URL: file_id={file_id}")
|
api_logger.info(f"Generated file URL: file_id={file_id}")
|
||||||
@@ -786,7 +790,7 @@ async def permanent_download_file(
|
|||||||
# For remote storage, redirect to presigned URL with long expiration
|
# For remote storage, redirect to presigned URL with long expiration
|
||||||
try:
|
try:
|
||||||
# Use a very long expiration (7 days max for most cloud providers)
|
# Use a very long expiration (7 days max for most cloud providers)
|
||||||
presigned_url = await storage_service.get_file_url(file_key, expires=604800)
|
presigned_url = await storage_service.get_file_url(file_key, expires=604800, file_name=file_metadata.file_name)
|
||||||
presigned_url = _match_scheme(request, presigned_url)
|
presigned_url = _match_scheme(request, presigned_url)
|
||||||
return RedirectResponse(url=presigned_url, status_code=status.HTTP_302_FOUND)
|
return RedirectResponse(url=presigned_url, status_code=status.HTTP_302_FOUND)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
@@ -669,6 +669,7 @@ async def config_query(
|
|||||||
content = {
|
content = {
|
||||||
"app_type": release.app.type,
|
"app_type": release.app.type,
|
||||||
"variables": release.config.get("variables"),
|
"variables": release.config.get("variables"),
|
||||||
|
"memory": release.config.get("memory", {}).get("enabled"),
|
||||||
"features": release.config.get("features")
|
"features": release.config.get("features")
|
||||||
}
|
}
|
||||||
elif release.app.type == AppType.MULTI_AGENT:
|
elif release.app.type == AppType.MULTI_AGENT:
|
||||||
|
|||||||
@@ -109,17 +109,13 @@ class StorageBackend(ABC):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
async def get_url(self, file_key: str, expires: int = 3600) -> str:
|
async def get_url(
|
||||||
"""
|
self,
|
||||||
Get an access URL for the file.
|
file_key: str,
|
||||||
|
expires: int = 3600,
|
||||||
Args:
|
file_name: Optional[str] = None
|
||||||
file_key: Unique identifier for the file in the storage system.
|
) -> str:
|
||||||
expires: URL validity period in seconds (default: 1 hour).
|
"""Get an access URL for the file."""
|
||||||
|
|
||||||
Returns:
|
|
||||||
URL for accessing the file.
|
|
||||||
"""
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def get_permanent_url(self, file_key: str) -> Optional[str]:
|
async def get_permanent_url(self, file_key: str) -> Optional[str]:
|
||||||
|
|||||||
@@ -210,7 +210,12 @@ class LocalStorage(StorageBackend):
|
|||||||
cause=e,
|
cause=e,
|
||||||
)
|
)
|
||||||
|
|
||||||
async def get_url(self, file_key: str, expires: int = 3600) -> str:
|
async def get_url(
|
||||||
|
self,
|
||||||
|
file_key: str,
|
||||||
|
expires: int = 3600,
|
||||||
|
file_name: Optional[str] = None
|
||||||
|
) -> str:
|
||||||
"""
|
"""
|
||||||
Get an access URL for the file.
|
Get an access URL for the file.
|
||||||
|
|
||||||
@@ -220,6 +225,7 @@ class LocalStorage(StorageBackend):
|
|||||||
Args:
|
Args:
|
||||||
file_key: Unique identifier for the file in the storage system.
|
file_key: Unique identifier for the file in the storage system.
|
||||||
expires: URL validity period in seconds (not used for local storage).
|
expires: URL validity period in seconds (not used for local storage).
|
||||||
|
file_name: If set, adds Content-Disposition: attachment to force download.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
A relative URL path for accessing the file.
|
A relative URL path for accessing the file.
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ Storage Service (OSS) using the oss2 SDK.
|
|||||||
|
|
||||||
import io
|
import io
|
||||||
import logging
|
import logging
|
||||||
|
import urllib.parse
|
||||||
from typing import AsyncIterator, Optional
|
from typing import AsyncIterator, Optional
|
||||||
|
|
||||||
import oss2
|
import oss2
|
||||||
@@ -242,24 +243,33 @@ class OSSStorage(StorageBackend):
|
|||||||
logger.error(f"Failed to check file existence in OSS {file_key}: {e}")
|
logger.error(f"Failed to check file existence in OSS {file_key}: {e}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
async def get_url(self, file_key: str, expires: int = 3600) -> str:
|
async def get_url(
|
||||||
|
self,
|
||||||
|
file_key: str,
|
||||||
|
expires: int = 3600,
|
||||||
|
file_name: Optional[str] = None,
|
||||||
|
) -> str:
|
||||||
"""
|
"""
|
||||||
Get a presigned URL for accessing the file.
|
Get a presigned URL for accessing the file.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
file_key: Unique identifier for the file in the storage system.
|
file_key: Unique identifier for the file in the storage system.
|
||||||
expires: URL validity period in seconds (default: 1 hour).
|
expires: URL validity period in seconds (default: 1 hour).
|
||||||
|
file_name: If set, adds Content-Disposition: attachment to force download.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
A presigned URL for accessing the file.
|
A presigned URL for accessing the file.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
url = self.bucket.sign_url("GET", file_key, expires)
|
params = {}
|
||||||
|
if file_name:
|
||||||
|
filename_encoded = urllib.parse.quote(file_name.encode("utf-8"))
|
||||||
|
params["response-content-disposition"] = f"attachment; filename*=UTF-8''{filename_encoded}"
|
||||||
|
url = self.bucket.sign_url("GET", file_key, expires, params=params if params else None)
|
||||||
logger.debug(f"Generated presigned URL for {file_key}, expires in {expires}s")
|
logger.debug(f"Generated presigned URL for {file_key}, expires in {expires}s")
|
||||||
return url
|
return url
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Failed to generate presigned URL for {file_key}: {e}")
|
logger.error(f"Failed to generate presigned URL for {file_key}: {e}")
|
||||||
# Return a basic URL format as fallback
|
|
||||||
return f"https://{self.bucket_name}.{self.endpoint.replace('https://', '').replace('http://', '')}/{file_key}"
|
return f"https://{self.bucket_name}.{self.endpoint.replace('https://', '').replace('http://', '')}/{file_key}"
|
||||||
|
|
||||||
async def get_permanent_url(self, file_key: str) -> str:
|
async def get_permanent_url(self, file_key: str) -> str:
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ using the boto3 SDK.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import io
|
import io
|
||||||
|
import urllib.parse
|
||||||
import logging
|
import logging
|
||||||
from typing import AsyncIterator, Optional
|
from typing import AsyncIterator, Optional
|
||||||
|
|
||||||
@@ -352,31 +353,37 @@ class S3Storage(StorageBackend):
|
|||||||
logger.error(f"Failed to check file existence in S3 {file_key}: {e}")
|
logger.error(f"Failed to check file existence in S3 {file_key}: {e}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
async def get_url(self, file_key: str, expires: int = 3600) -> str:
|
async def get_url(
|
||||||
|
self,
|
||||||
|
file_key: str,
|
||||||
|
expires: int = 3600,
|
||||||
|
file_name: Optional[str] = None,
|
||||||
|
) -> str:
|
||||||
"""
|
"""
|
||||||
Get a presigned URL for accessing the file.
|
Get a presigned URL for accessing the file.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
file_key: Unique identifier for the file in the storage system.
|
file_key: Unique identifier for the file in the storage system.
|
||||||
expires: URL validity period in seconds (default: 1 hour).
|
expires: URL validity period in seconds (default: 1 hour).
|
||||||
|
file_name: If set, adds Content-Disposition: attachment to force download.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
A presigned URL for accessing the file.
|
A presigned URL for accessing the file.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
|
params = {"Bucket": self.bucket_name, "Key": file_key}
|
||||||
|
if file_name:
|
||||||
|
filename_encoded = urllib.parse.quote(file_name.encode("utf-8"))
|
||||||
|
params["ResponseContentDisposition"] = f"attachment; filename*=UTF-8''{filename_encoded}"
|
||||||
url = self.client.generate_presigned_url(
|
url = self.client.generate_presigned_url(
|
||||||
"get_object",
|
"get_object",
|
||||||
Params={
|
Params=params,
|
||||||
"Bucket": self.bucket_name,
|
|
||||||
"Key": file_key,
|
|
||||||
},
|
|
||||||
ExpiresIn=expires,
|
ExpiresIn=expires,
|
||||||
)
|
)
|
||||||
logger.debug(f"Generated presigned URL for {file_key}, expires in {expires}s")
|
logger.debug(f"Generated presigned URL for {file_key}, expires in {expires}s")
|
||||||
return url
|
return url
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Failed to generate presigned URL for {file_key}: {e}")
|
logger.error(f"Failed to generate presigned URL for {file_key}: {e}")
|
||||||
# Return a basic URL format as fallback
|
|
||||||
return f"https://{self.bucket_name}.s3.{self.region}.amazonaws.com/{file_key}"
|
return f"https://{self.bucket_name}.s3.{self.region}.amazonaws.com/{file_key}"
|
||||||
|
|
||||||
async def get_permanent_url(self, file_key: str) -> str:
|
async def get_permanent_url(self, file_key: str) -> str:
|
||||||
|
|||||||
@@ -325,27 +325,30 @@ class FileStorageService:
|
|||||||
)
|
)
|
||||||
raise
|
raise
|
||||||
|
|
||||||
async def get_file_url(self, file_key: str, expires: int = 3600) -> str:
|
async def get_file_url(
|
||||||
|
self,
|
||||||
|
file_key: str,
|
||||||
|
expires: int = 3600,
|
||||||
|
file_name: Optional[str] = None,
|
||||||
|
) -> str:
|
||||||
"""
|
"""
|
||||||
Get an access URL for a file.
|
Get an access URL for a file.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
file_key: The file key.
|
file_key: The file key.
|
||||||
expires: URL validity period in seconds (default: 1 hour).
|
expires: URL validity period in seconds (default: 1 hour).
|
||||||
|
file_name: If set, adds Content-Disposition: attachment to force download.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
URL for accessing the file.
|
URL for accessing the file.
|
||||||
"""
|
"""
|
||||||
logger.debug(f"Getting file URL: file_key={file_key}, expires={expires}s")
|
logger.debug(f"Getting file URL: file_key={file_key}, expires={expires}s")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
url = await self.storage.get_url(file_key, expires)
|
url = await self.storage.get_url(file_key, expires, file_name=file_name)
|
||||||
logger.debug(f"File URL generated: file_key={file_key}")
|
logger.debug(f"File URL generated: file_key={file_key}")
|
||||||
return url
|
return url
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(
|
logger.error(f"Error getting file URL: file_key={file_key}, error={str(e)}")
|
||||||
f"Error getting file URL: file_key={file_key}, error={str(e)}"
|
|
||||||
)
|
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user