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
|
||||
url = generate_signed_url(str(file_id), expires)
|
||||
else:
|
||||
# For remote storage (OSS/S3), get presigned URL
|
||||
url = await storage_service.get_file_url(file_key, expires=expires)
|
||||
# For remote storage (OSS/S3), get presigned URL with forced download
|
||||
url = await storage_service.get_file_url(
|
||||
file_key,
|
||||
expires=expires,
|
||||
file_name=file_metadata.file_name,
|
||||
)
|
||||
url = _match_scheme(request, url)
|
||||
|
||||
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
|
||||
try:
|
||||
# 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)
|
||||
return RedirectResponse(url=presigned_url, status_code=status.HTTP_302_FOUND)
|
||||
except Exception as e:
|
||||
|
||||
@@ -669,6 +669,7 @@ async def config_query(
|
||||
content = {
|
||||
"app_type": release.app.type,
|
||||
"variables": release.config.get("variables"),
|
||||
"memory": release.config.get("memory", {}).get("enabled"),
|
||||
"features": release.config.get("features")
|
||||
}
|
||||
elif release.app.type == AppType.MULTI_AGENT:
|
||||
|
||||
@@ -109,17 +109,13 @@ class StorageBackend(ABC):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def get_url(self, file_key: str, expires: int = 3600) -> str:
|
||||
"""
|
||||
Get an access URL for the file.
|
||||
|
||||
Args:
|
||||
file_key: Unique identifier for the file in the storage system.
|
||||
expires: URL validity period in seconds (default: 1 hour).
|
||||
|
||||
Returns:
|
||||
URL for accessing the file.
|
||||
"""
|
||||
async def get_url(
|
||||
self,
|
||||
file_key: str,
|
||||
expires: int = 3600,
|
||||
file_name: Optional[str] = None
|
||||
) -> str:
|
||||
"""Get an access URL for the file."""
|
||||
pass
|
||||
|
||||
async def get_permanent_url(self, file_key: str) -> Optional[str]:
|
||||
|
||||
@@ -210,7 +210,12 @@ class LocalStorage(StorageBackend):
|
||||
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.
|
||||
|
||||
@@ -220,6 +225,7 @@ class LocalStorage(StorageBackend):
|
||||
Args:
|
||||
file_key: Unique identifier for the file in the storage system.
|
||||
expires: URL validity period in seconds (not used for local storage).
|
||||
file_name: If set, adds Content-Disposition: attachment to force download.
|
||||
|
||||
Returns:
|
||||
A relative URL path for accessing the file.
|
||||
|
||||
@@ -7,6 +7,7 @@ Storage Service (OSS) using the oss2 SDK.
|
||||
|
||||
import io
|
||||
import logging
|
||||
import urllib.parse
|
||||
from typing import AsyncIterator, Optional
|
||||
|
||||
import oss2
|
||||
@@ -242,24 +243,33 @@ class OSSStorage(StorageBackend):
|
||||
logger.error(f"Failed to check file existence in OSS {file_key}: {e}")
|
||||
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.
|
||||
|
||||
Args:
|
||||
file_key: Unique identifier for the file in the storage system.
|
||||
expires: URL validity period in seconds (default: 1 hour).
|
||||
file_name: If set, adds Content-Disposition: attachment to force download.
|
||||
|
||||
Returns:
|
||||
A presigned URL for accessing the file.
|
||||
"""
|
||||
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")
|
||||
return url
|
||||
except Exception as 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}"
|
||||
|
||||
async def get_permanent_url(self, file_key: str) -> str:
|
||||
|
||||
@@ -6,6 +6,7 @@ using the boto3 SDK.
|
||||
"""
|
||||
|
||||
import io
|
||||
import urllib.parse
|
||||
import logging
|
||||
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}")
|
||||
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.
|
||||
|
||||
Args:
|
||||
file_key: Unique identifier for the file in the storage system.
|
||||
expires: URL validity period in seconds (default: 1 hour).
|
||||
file_name: If set, adds Content-Disposition: attachment to force download.
|
||||
|
||||
Returns:
|
||||
A presigned URL for accessing the file.
|
||||
"""
|
||||
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(
|
||||
"get_object",
|
||||
Params={
|
||||
"Bucket": self.bucket_name,
|
||||
"Key": file_key,
|
||||
},
|
||||
Params=params,
|
||||
ExpiresIn=expires,
|
||||
)
|
||||
logger.debug(f"Generated presigned URL for {file_key}, expires in {expires}s")
|
||||
return url
|
||||
except Exception as 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}"
|
||||
|
||||
async def get_permanent_url(self, file_key: str) -> str:
|
||||
|
||||
@@ -325,27 +325,30 @@ class FileStorageService:
|
||||
)
|
||||
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.
|
||||
|
||||
Args:
|
||||
file_key: The file key.
|
||||
expires: URL validity period in seconds (default: 1 hour).
|
||||
file_name: If set, adds Content-Disposition: attachment to force download.
|
||||
|
||||
Returns:
|
||||
URL for accessing the file.
|
||||
"""
|
||||
logger.debug(f"Getting file URL: file_key={file_key}, expires={expires}s")
|
||||
|
||||
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}")
|
||||
return url
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"Error getting file URL: file_key={file_key}, error={str(e)}"
|
||||
)
|
||||
logger.error(f"Error getting file URL: file_key={file_key}, error={str(e)}")
|
||||
raise
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user