v1.9.5: S3 — new folder, folder delete with confirmation, folder drag-and-drop
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
18
core/i18n.py
18
core/i18n.py
@@ -390,6 +390,12 @@ _EN = {
|
||||
"s3_uploading_n": "Uploading {count} files...",
|
||||
"s3_uploaded_n": "Uploaded {count} files",
|
||||
"s3_upload_partial": "Uploaded {ok}/{total} files",
|
||||
"s3_new_folder": "New Folder",
|
||||
"s3_folder_name_prompt": "Folder name:",
|
||||
"s3_creating_folder": "Creating folder...",
|
||||
"s3_folder_failed": "Failed to create folder",
|
||||
"s3_delete_folder_confirm": "Delete folder \"{folder}\" and all its contents?",
|
||||
"s3_deleted_n": "Deleted {count} objects",
|
||||
"s3_copy_link_48h": "Copy Link (48h)",
|
||||
"s3_copy_link_permanent": "Copy Direct Link",
|
||||
"s3_generating_link": "Generating link...",
|
||||
@@ -897,6 +903,12 @@ _RU = {
|
||||
"s3_uploading_n": "Загрузка {count} файлов...",
|
||||
"s3_uploaded_n": "Загружено {count} файлов",
|
||||
"s3_upload_partial": "Загружено {ok}/{total} файлов",
|
||||
"s3_new_folder": "Новая папка",
|
||||
"s3_folder_name_prompt": "Имя папки:",
|
||||
"s3_creating_folder": "Создание папки...",
|
||||
"s3_folder_failed": "Ошибка создания папки",
|
||||
"s3_delete_folder_confirm": "Удалить папку \"{folder}\" со всем содержимым?",
|
||||
"s3_deleted_n": "Удалено {count} объектов",
|
||||
"s3_copy_link_48h": "Ссылка (48ч)",
|
||||
"s3_copy_link_permanent": "Прямая ссылка",
|
||||
"s3_generating_link": "Генерация ссылки...",
|
||||
@@ -1404,6 +1416,12 @@ _ZH = {
|
||||
"s3_uploading_n": "正在上传 {count} 个文件...",
|
||||
"s3_uploaded_n": "已上传 {count} 个文件",
|
||||
"s3_upload_partial": "已上传 {ok}/{total} 个文件",
|
||||
"s3_new_folder": "新建文件夹",
|
||||
"s3_folder_name_prompt": "文件夹名称:",
|
||||
"s3_creating_folder": "创建文件夹中...",
|
||||
"s3_folder_failed": "创建文件夹失败",
|
||||
"s3_delete_folder_confirm": "删除文件夹 \"{folder}\" 及其所有内容?",
|
||||
"s3_deleted_n": "已删除 {count} 个对象",
|
||||
"s3_copy_link_48h": "复制链接 (48小时)",
|
||||
"s3_copy_link_permanent": "复制直接链接",
|
||||
"s3_generating_link": "生成链接中...",
|
||||
|
||||
@@ -306,6 +306,43 @@ class S3Client:
|
||||
log.error("S3 presigned URL failed: %s", exc)
|
||||
return None
|
||||
|
||||
def delete_prefix(self, bucket: str, prefix: str) -> int:
|
||||
"""Recursively delete all objects under a prefix. Returns count deleted."""
|
||||
if not self._ensure_connected():
|
||||
return 0
|
||||
try:
|
||||
deleted = 0
|
||||
paginator = self._client.get_paginator("list_objects_v2")
|
||||
for page in paginator.paginate(Bucket=bucket, Prefix=prefix):
|
||||
objects = page.get("Contents", [])
|
||||
if not objects:
|
||||
continue
|
||||
delete_req = {
|
||||
"Objects": [{"Key": obj["Key"]} for obj in objects],
|
||||
"Quiet": True,
|
||||
}
|
||||
self._client.delete_objects(Bucket=bucket, Delete=delete_req)
|
||||
deleted += len(objects)
|
||||
self._last_ok = time.time()
|
||||
log.info("S3 deleted prefix s3://%s/%s (%d objects)", bucket, prefix, deleted)
|
||||
return deleted
|
||||
except Exception as exc:
|
||||
log.error("S3 delete prefix failed: %s", exc)
|
||||
return 0
|
||||
|
||||
def create_folder(self, bucket: str, key: str) -> bool:
|
||||
"""Create a folder (empty object with trailing slash) in S3."""
|
||||
if not self._ensure_connected():
|
||||
return False
|
||||
try:
|
||||
self._client.put_object(Bucket=bucket, Key=key, Body=b"")
|
||||
self._last_ok = time.time()
|
||||
log.info("S3 created folder s3://%s/%s", bucket, key)
|
||||
return True
|
||||
except Exception as exc:
|
||||
log.error("S3 create folder failed: %s", exc)
|
||||
return False
|
||||
|
||||
def get_direct_url(self, bucket: str, key: str) -> str:
|
||||
"""Build a direct (permanent) URL: endpoint/bucket/key."""
|
||||
endpoint = self._endpoint.rstrip("/")
|
||||
|
||||
Reference in New Issue
Block a user