tamar-file-hub-client 0.2.7
A Python SDK for gRPC-based file management system
License: MIT
Requires Python: >=3.8

# File Hub Client

一个基于 gRPC 的文件管理系统 Python SDK，提供异步和同步两种客户端实现。

## 功能特性

- 🚀 **双模式支持**：提供异步（AsyncIO）和同步两种客户端实现
- 📁 **完整的文件管理**：支持文件上传、下载、重命名、删除等操作
- 📂 **文件夹管理**：支持文件夹的创建、重命名、移动、删除
- 🔗 **文件分享**：支持生成分享链接，设置访问权限和密码
- 🔄 **多种上传方式**：支持直传、断点续传、客户端直传到对象存储
- 🎯 **智能MIME类型检测**：支持26+种主流文件格式的魔术字节检测和扩展名推断
- 🤖 **AI生成文件支持**：完美支持AI模型输出的字节数据+MIME类型组合上传
- 🛡️ **错误处理**：完善的异常体系和错误重试机制
- 🔒 **TLS/SSL 支持**：支持安全的加密连接，保护数据传输
- 🔁 **自动重试**：连接失败时自动重试，提高可靠性
- 📝 **类型注解**：完整的类型提示支持
- 🧩 **模块化设计**：清晰的代码结构，易于扩展
- 🎨 **图片和视频压缩**：支持多种规格的媒体文件压缩变体生成和管理
- 📊 **批量状态查询**：支持批量查询文件的上传、压缩、备份同步状态
- 🏗️ **分层服务架构**：文件服务分为传统文件（blob）和自定义类型（结构化数据），每种类型独立服务，语义清晰
- 🔧 **环境变量配置**：支持通过环境变量配置所有参数
- 👤 **用户上下文管理**：支持区分资源所有权（ownership）和操作者（operator）
- 📊 **请求上下文追踪**：自动收集客户端信息，支持请求追踪和审计
- 📊 **Taple 电子表格**：完整的类 Excel 功能支持，包括数据导入导出、查询筛选、样式管理等
- 📡 **gRPC 请求日志**：自动记录所有 gRPC 请求和响应，支持 JSON 格式日志

## 项目结构

```
file-hub-client/
├── file_hub_client/              # 主包目录
│   ├── __init__.py              # 包初始化，导出版本信息和主要类
│   ├── client.py                # 客户端入口（AsyncTamarFileHubClient, TamarFileHubClient）
│   ├── py.typed                 # PEP 561 类型标记文件
│   │
│   ├── rpc/                     # gRPC 相关
│   │   ├── __init__.py         # RPC 模块初始化
│   │   ├── async_client.py     # 异步 gRPC 客户端基类
│   │   ├── sync_client.py      # 同步 gRPC 客户端基类
│   │   ├── interceptors.py     # gRPC 拦截器（自动日志记录）
│   │   ├── generate_grpc.py    # Proto 文件代码生成脚本
│   │   ├── protos/             # Protocol Buffer 定义
│   │   │   ├── file_service.proto    # 文件服务定义
│   │   │   ├── folder_service.proto  # 文件夹服务定义
│   │   │   └── taple_service.proto   # Taple 服务定义
│   │   └── gen/                # 生成的 gRPC 代码（自动生成）
│   │       ├── __init__.py
│   │       ├── file_service_pb2.py
│   │       ├── file_service_pb2_grpc.py
│   │       ├── folder_service_pb2.py
│   │       └── folder_service_pb2_grpc.py
│   │
│   ├── services/                # 服务层（分层架构：传统文件用blob_service，自定义类型独立service）
│   │   ├── __init__.py         # 服务模块导出
│   │   ├── file/               # 文件服务（统一入口，按类型分层）
│   │   │   ├── __init__.py
│   │   │   ├── base_file_service.py     # 文件服务基类
│   │   │   ├── async_blob_service.py    # 异步二进制大对象服务（传统文件上传下载）
│   │   │   ├── sync_blob_service.py     # 同步二进制大对象服务（传统文件上传下载）
│   │   │   ├── async_file_service.py    # 异步文件元数据服务（所有类型通用）
│   │   │   └── sync_file_service.py     # 同步文件元数据服务（所有类型通用）
│   │   │   # 未来扩展：spreadsheet_service, document_service, canvas_service等
│   │   ├── folder/             # 文件夹服务
│   │   │   ├── __init__.py
│   │   │   ├── async_folder_service.py  # 异步文件夹服务实现
│   │   │   └── sync_folder_service.py   # 同步文件夹服务实现
│   │   └── taple/              # Taple（电子表格）服务
│   │       ├── __init__.py
│   │       ├── base_taple_service.py    # Taple 服务基类
│   │       ├── async_taple_service.py   # 异步 Taple 服务实现
│   │       ├── sync_taple_service.py    # 同步 Taple 服务实现
│   │       └── idempotent_taple_mixin.py # 幂等性支持混入类
│   │
│   ├── schemas/                 # 数据模型（Pydantic）
│   │   ├── __init__.py         # 模型导出
│   │   ├── file.py             # 文件相关模型
│   │   ├── folder.py           # 文件夹相关模型
│   │   ├── context.py          # 上下文相关模型（用户和请求上下文）
│   │   └── taple.py            # Taple 相关模型
│   │
│   ├── enums/                   # 枚举定义
│   │   ├── __init__.py         # 枚举导出
│   │   ├── role.py             # 角色枚举（ACCOUNT, AGENT, SYSTEM）
│   │   ├── upload_mode.py      # 上传模式枚举
│   │   └── export_format.py    # 导出格式枚举
│   │
│   ├── errors/                  # 异常定义
│   │   ├── __init__.py         # 异常导出
│   │   └── exceptions.py       # 自定义异常类
│   │
│   └── utils/                   # 工具函数
│       ├── __init__.py         # 工具函数导出
│       ├── file_utils.py       # 文件操作工具
│       ├── converter.py        # 数据转换工具
│       ├── retry.py            # 重试装饰器
│       ├── upload_helper.py    # 上传辅助工具（HTTP上传器）
│       ├── download_helper.py  # 下载辅助工具（HTTP下载器）
│       ├── idempotency.py      # 幂等性支持工具
│       └── logging.py          # 日志配置和工具
│
├── tests/                      # 测试文件
│   └── taple/                  # Taple 功能测试
│       ├── config.py           # 测试配置
│       ├── test_*.py           # 各种功能测试脚本
│       └── run_all_tests.py   # 运行所有测试
│
├── .gitignore                  # Git 忽略文件配置
├── .env.example                # 环境变量配置示例
├── README.md                   # 项目说明文档（本文件）
├── setup.py                    # 安装配置文件
├── pyproject.toml             # 项目配置文件（PEP 518）
└── MANIFEST.in                # 打包配置文件
```

## 模块说明

### 核心模块

- **client.py**: 提供 `AsyncTamarFileHubClient` 和 `TamarFileHubClient` 两个客户端类，是使用 SDK 的入口点
  - 提供了预配置的单例客户端 `tamar_client` 和 `async_tamar_client`
  - 支持分层服务访问：
    - `blobs`（传统文件内容：上传/下载）
    - `files`（文件元数据：所有类型通用的管理操作）
    - `folders`（文件夹管理）
    - `taples`（电子表格服务）
    - 未来扩展：`documents`、`canvases` 等自定义类型服务

### RPC 模块 (`rpc/`)

- **async_client.py/sync_client.py**: gRPC 客户端基类，处理连接管理、元数据构建、stub 缓存
- **interceptors.py**: gRPC 拦截器，自动记录所有请求和响应日志
- **generate_grpc.py**: 从 proto 文件生成 Python 代码的脚本
- **protos/**: 存放 Protocol Buffer 定义文件
    - `file_service.proto`: 定义文件相关的 RPC 服务
    - `folder_service.proto`: 定义文件夹相关的 RPC 服务
    - `taple_service.proto`: 定义 Taple 电子表格相关的 RPC 服务

### 服务模块 (`services/`)

#### 分层服务架构设计

File Hub Client 采用分层服务架构，将文件服务按类型和语义进行清晰分离：

**📁 统一文件入口**：所有文件类型都通过统一的 `files` 接口进行元数据管理（获取、重命名、删除、列表等）

**🔄 按类型分层服务**：
- **传统文件类型**（PDF、图片、视频等）→ `blob_service` 处理
  - 核心操作：**上传** 和 **下载**
  - 特点：二进制数据，重点是存储和传输
  
- **自定义文件类型**（在线表格、文档、画布等）→ 每种类型独立 `service`
  - 核心操作：**创建** 和 **导出**
  - 特点：结构化数据，重点是数据操作和格式转换

**🎯 设计优势**：
- **语义清晰**：不同类型的文件使用不同的操作语义，更符合实际使用场景
- **易于扩展**：新增自定义文件类型时，只需添加对应的独立服务
- **职责分离**：每个服务专注于特定类型的操作，代码更易维护
- **SDK 友好**：为 SDK 使用者提供更直观的 API 设计，而非通用的 REST API

#### 具体实现

- **file/**: 文件服务实现
    - **blob_service**: 处理传统文件（二进制大对象）
        - 支持多种上传模式（普通上传、流式上传、断点续传）
        - 智能选择上传模式（根据文件大小）
        - 生成上传/下载 URL
        - 支持临时文件上传
        - **媒体文件压缩**：支持图片和视频的多规格压缩变体生成
        - **压缩管理**：获取压缩状态、管理变体、触发重新压缩
        - **批量文件状态查询**：一次性查询多个文件的上传、压缩、同步状态
        - 适用类型：PDF、图片、视频、音频、压缩包等
    - **file_service**: 处理文件元数据操作（所有类型通用）
        - 获取、重命名、删除文件
        - 列出文件
        - 生成分享链接
        - 记录文件访问
    - **[future] document_service**: 在线文档服务（规划中）  
        - 创建文档、编辑内容、插入元素
        - 导出为 Word、PDF、HTML 等格式
    - **[future] canvas_service**: 画布服务（规划中）
        - 创建画布、绘制图形、添加元素
        - 导出为 PNG、SVG、PDF 等格式

- **folder/**: 文件夹服务实现
    - 创建、重命名、移动、删除文件夹
    - 列出文件夹内容

- **taple/**: Taple 电子表格服务实现（已上线）
    - **taple_service**: 基础表格服务
        - 创建表格、工作表、列、行、单元格
        - 支持批量操作和乐观锁版本控制
        - 合并单元格和视图管理
    - **idempotent_taple_mixin**: 幂等性支持
        - 自动管理幂等性键
        - 防止重复操作

### 数据模型 (`schemas/`)

- **file.py**: 文件相关的数据模型
    - `File`: 文件信息
    - `FileUploadResponse`: 文件上传响应
    - `UploadUrlResponse`: URL上传响应
    - `ShareLinkRequest`: 分享链接请求
    - `FileListResponse`: 文件列表响应
    - `CompressedVariant`: 压缩变体信息
    - `CompressionStatusResponse`: 压缩状态响应
    - `GetVariantsResponse`: 获取变体响应
    - `RecompressionResponse`: 重新压缩响应
    - `VariantDownloadUrlResponse`: 变体下载URL响应
    - `BatchFileStatusResponse`: 批量文件状态响应
    - `FileStatusInfo`: 单个文件状态信息
    - `FileStatusDetails`: 文件状态详细信息
    - `FileUploadStatus`: 文件上传状态枚举
    - `FileCompressionStatus`: 文件压缩状态枚举
    - `FileSyncStatus`: 文件同步状态枚举

- **folder.py**: 文件夹相关的数据模型
    - `FolderInfo`: 文件夹信息
    - `FolderListResponse`: 文件夹列表响应

- **context.py**: 上下文相关的数据模型
    - `UserContext`: 用户上下文（组织、用户、角色、操作者）
    - `RequestContext`: 请求上下文（请求ID、客户端信息、追踪信息）
    - `FullContext`: 完整上下文

- **taple.py**: Taple 相关的数据模型
    - `Table`: 表格信息
    - `Sheet`: 工作表信息
    - `Column`: 列信息
    - `Row`: 行信息
    - `Cell`: 单元格信息
    - `ConflictInfo`: 冲突信息
    - `BatchEditSheetResponse`: 批量编辑响应

### 枚举定义 (`enums/`)

- **role.py**: 用户角色枚举（ACCOUNT、AGENT、SYSTEM）
- **upload_mode.py**: 上传模式枚举（NORMAL、STREAM、RESUMABLE）
- **export_format.py**: 导出格式枚举（XLSX、CSV、JSON、HTML、MARKDOWN）

### 工具模块 (`utils/`)

- **file_utils.py**: 文件操作相关工具函数
    - `get_file_mime_type`: 获取文件 MIME 类型（支持自定义映射）
    - `split_file_chunks`: 文件分块
    - `calculate_file_hash`: 计算文件哈希

- **converter.py**: 数据转换工具
    - `timestamp_to_datetime`: 时间戳转换
    - `convert_proto_to_model`: Proto 消息转模型

- **retry.py**: 提供重试装饰器 `retry_with_backoff`

- **upload_helper.py**: HTTP 上传辅助工具
    - `AsyncHttpUploader`: 异步 HTTP 上传器
    - `SyncHttpUploader`: 同步 HTTP 上传器
    - 支持普通上传和断点续传

- **download_helper.py**: HTTP 下载辅助工具
    - `AsyncHttpDownloader`: 异步 HTTP 下载器
    - `SyncHttpDownloader`: 同步 HTTP 下载器
    - 支持流式下载和断点续传

- **idempotency.py**: 幂等性支持工具
    - `IdempotencyKeyGenerator`: 幂等性键生成器
    - `IdempotencyManager`: 幂等性管理器
    - `generate_idempotency_key`: 生成幂等性键函数

- **logging.py**: 日志配置和工具
    - `GrpcJSONFormatter`: JSON 格式化器
    - `GrpcRequestLogger`: gRPC 请求日志记录器
    - 支持中文日志消息和图标

### 错误处理 (`errors/`)

- **exceptions.py**: 定义了完整的异常体系
    - `FileHubError`: 基础异常类
    - `FileNotFoundError`: 文件不存在
    - `FolderNotFoundError`: 文件夹不存在
    - `UploadError`: 上传错误
    - `DownloadError`: 下载错误
    - `ValidationError`: 验证错误
    - `ConnectionError`: 连接错误
    - `TimeoutError`: 超时错误
    - `PermissionError`: 权限错误
    - 等等...

## 安装

```bash
pip install tamar-file-hub-client
```

## 配置

### 环境变量配置

File Hub Client 支持通过环境变量配置连接参数，这在生产环境中特别有用。

1. **创建 `.env` 文件**：
   ```bash
   # 在项目根目录创建 .env 文件
   touch .env
   ```

2. **编辑 `.env` 文件**：
   
   **线上环境示例（使用域名，不需要端口）**：
   ```env
   # gRPC 服务器配置 - 线上环境
   FILE_HUB_HOST=api.filehub.example.com
   # FILE_HUB_PORT 不设置，使用域名默认端口
   FILE_HUB_SECURE=true
   FILE_HUB_API_KEY=your-api-key
   
   # 连接重试配置
   FILE_HUB_RETRY_COUNT=5
   FILE_HUB_RETRY_DELAY=2.0
   ```
   
   **本地开发环境示例（使用自定义端口）**：
   ```env
   # gRPC 服务器配置 - 本地开发
   FILE_HUB_HOST=localhost
   FILE_HUB_PORT=50051
   FILE_HUB_SECURE=false
   # FILE_HUB_API_KEY 本地开发可能不需要
   
   # 连接重试配置
   FILE_HUB_RETRY_COUNT=3
   FILE_HUB_RETRY_DELAY=1.0
   ```

3. **支持的环境变量**：

   | 环境变量 | 说明 | 默认值 |
   |---------|------|--------|
   | `FILE_HUB_HOST` | gRPC 服务器地址（域名或IP） | `localhost` |
   | `FILE_HUB_PORT` | gRPC 服务器端口（可选，不设置时直接使用HOST） | 无 |
   | `FILE_HUB_SECURE` | 是否启用 TLS/SSL | `false` |
   | `FILE_HUB_API_KEY` | API 认证密钥（可选） | 无 |
   | `FILE_HUB_RETRY_COUNT` | 连接重试次数 | `3` |
   | `FILE_HUB_RETRY_DELAY` | 重试延迟（秒） | `1.0` |

### TLS/SSL 配置

当 `FILE_HUB_SECURE` 设置为 `true` 时，客户端会使用 TLS 加密连接：

- 默认使用系统的根证书
- 如果提供了 `FILE_HUB_API_KEY`，会自动添加到请求头中进行认证

```python
# 通过代码配置 TLS
from file_hub_client import TamarFileHubClient

# 方式1：使用域名（不需要指定端口）
client = TamarFileHubClient(
    host="secure-server.com",  # 只需要域名
    secure=True,
    credentials={"api_key": "your-api-key"}
)

# 方式2：使用自定义端口
client = TamarFileHubClient(
    host="secure-server.com",
    port=8443,  # 自定义端口
    secure=True,
    credentials={"api_key": "your-api-key"}
)
```

### 端口配置说明

从 v0.0.3 版本开始，端口参数变为可选：

- **线上环境**：通常只需要提供域名，不需要指定端口
- **本地开发**：可以指定自定义端口

```python
# 线上环境（使用标准端口）
client = TamarFileHubClient(
    host="api.example.com",  # 只提供域名
    secure=True
)

# 本地开发（使用自定义端口）
client = TamarFileHubClient(
    host="localhost",
    port=50051,  # 自定义端口
    secure=False
)
```

### 连接重试

客户端支持自动重试连接，对于不稳定的网络环境特别有用：

```python
# 通过代码配置重试
from file_hub_client import TamarFileHubClient

client = TamarFileHubClient(
    host="server.com",
    retry_count=5,  # 重试5次
    retry_delay=2.0  # 每次重试间隔2秒
)
```

### 日志配置

File Hub Client 支持详细的 gRPC 请求日志记录：

```python
from file_hub_client import AsyncTamarFileHubClient

# 启用日志记录（默认启用）
client = AsyncTamarFileHubClient(
    enable_logging=True,
    log_level="INFO"  # DEBUG, INFO, WARNING, ERROR
)

# 日志输出示例（JSON格式）：
# {
#   "timestamp": "2025-07-15T17:30:00.123456",
#   "level": "INFO", 
#   "type": "request",
#   "uri": "CreateFolder",
#   "request_id": "test-123",
#   "data": {
#     "folder_name": "测试文件夹",
#     "parent_id": "parent-456"
#   },
#   "message": "📤 gRPC 请求: CreateFolder",
#   "logger": "file_hub_client.grpc"
# }
```

日志类型包括：
- 📡 初始化日志
- 📤 请求日志（包含请求参数）
- ✅ 响应日志（包含耗时）
- ❌ 错误日志
- 🔗 连接成功
- ⚠️ 连接重试
- 👋 关闭连接

### 加载环境变量

使用 `python-dotenv` 加载 `.env` 文件（需要额外安装）：

```bash
pip install python-dotenv
```

```python
from dotenv import load_dotenv
import os

# 加载 .env 文件
load_dotenv()

# 现在可以直接使用客户端，它会自动读取环境变量
from file_hub_client import AsyncTamarFileHubClient

# 示例1：如果 FILE_HUB_PORT 未设置，将使用域名作为完整地址
# .env: FILE_HUB_HOST=api.example.com, FILE_HUB_SECURE=true
async with AsyncTamarFileHubClient() as client:
    # 连接到 api.example.com（使用默认的 HTTPS 端口）
    pass

# 示例2：如果 FILE_HUB_PORT 设置了，将使用 host:port 格式
# .env: FILE_HUB_HOST=localhost, FILE_HUB_PORT=50051
async with AsyncTamarFileHubClient() as client:
    # 连接到 localhost:50051
    pass
```

### 配置优先级

客户端配置的优先级如下（从高到低）：

1. 直接传入的参数
2. 环境变量
3. 默认值

```python
# 示例：参数会覆盖环境变量
from file_hub_client import AsyncTamarFileHubClient

# 情况1：覆盖环境变量中的 host
client = AsyncTamarFileHubClient(
    host="override-host.com",  # 这会覆盖 FILE_HUB_HOST
    # port 将使用环境变量 FILE_HUB_PORT（如果设置了）
)

# 情况2：明确不使用端口（即使环境变量设置了端口）
client = AsyncTamarFileHubClient(
    host="api.production.com",
    port=None,  # 明确指定不使用端口，忽略 FILE_HUB_PORT
    secure=True
)
```

## 快速开始

### 文件上传

File Hub Client 提供了统一的上传接口，支持多种上传模式：

#### 上传模式

- **NORMAL（普通模式）**：适用于小文件，通过 gRPC 直接上传
- **STREAM（流式上传）**：适用于流式数据上传
- **RESUMABLE（断点续传）**：支持断点续传，适用于大文件和不稳定网络

#### 最简单的上传

```python
from file_hub_client import AsyncTamarFileHubClient

async with AsyncTamarFileHubClient() as client:
    # 设置用户上下文
    client.set_user_context(org_id="123", user_id="456")

    # 最简单的用法 - 只需要文件路径
    file_info = await client.blobs.upload(
        "path/to/document.pdf",
        folder_id="1dee0f7b-2e4f-45cd-a462-4e1d82df9bdd"  # 上传到指定文件夹，不传则默认文件夹
    )
    print(f"上传成功: {file_info.file.id}")
    print(f"文件类型: {file_info.file.file_type}")  # 自动识别为 "pdf"
```

#### 从URL上传文件

```python
from file_hub_client import AsyncTamarFileHubClient

async with AsyncTamarFileHubClient() as client:
    # 设置用户上下文
    client.set_user_context(org_id="123", user_id="456")

    # 从URL下载并上传文件（自动提取文件名）
    file_info = await client.blobs.upload(
        url="https://example.com/document.pdf"
    )
    print(f"上传成功: {file_info.file.id}")
    
    # 从URL上传并指定文件名
    file_info = await client.blobs.upload(
        url="https://example.com/some-file",
        file_name="my_document.pdf"  # 指定文件名
    )
    print(f"文件名: {file_info.file.file_name}")
```

#### 上传不同类型的内容

```python
from file_hub_client import AsyncTamarFileHubClient
from pathlib import Path

async with AsyncTamarFileHubClient() as client:
    # 1. 上传文件路径（字符串或Path对象）
    file_info = await client.blobs.upload("path/to/file.pdf")
    file_info = await client.blobs.upload(Path("path/to/file.pdf"))

    # 2. 上传字节数据（需要指定文件名）
    content = b"This is file content"
    file_info = await client.blobs.upload(
        content,
        file_name="document.txt"
    )

    # 3. 上传文件对象
    with open("image.png", "rb") as f:
        file_info = await client.blobs.upload(f)
```

#### AI生成文件上传（新功能）

```python
from file_hub_client import AsyncTamarFileHubClient

async with AsyncTamarFileHubClient() as client:
    # AI模型返回的字节数据（图片、音频、视频等）
    # 场景：AI生图模型返回WebP格式图片
    ai_image_data = b"\x52\x49\x46\x46...."  # WebP格式的字节数据
    
    # 方式1：显式指定MIME类型（推荐用于AI生成内容）
    file_info = await client.blobs.upload(
        file=ai_image_data,
        mime_type="image/webp"  # 明确指定MIME类型
    )
    print(f"AI生成图片上传成功: {file_info.file.file_name}")  # upload_xxx.webp
    
    # 方式2：自动检测MIME类型（支持26+种格式）
    file_info = await client.blobs.upload(file=ai_image_data)
    # 系统会自动检测magic bytes并推断为WebP格式
    
    # 支持的AI生成内容格式：
    # 🖼️ 图片: PNG, JPEG, WebP, GIF, BMP等
    # 🎵 音频: MP3, WAV, FLAC, AAC, OGG等  
    # 🎥 视频: MP4, MOV, WebM, AVI等
    # 📄 文档: PDF, TXT等
```

#### 大文件上传（流式上传和断点续传）

```python
from file_hub_client import AsyncTamarFileHubClient, UploadMode

async with AsyncTamarFileHubClient() as client:
    # 设置用户上下文
    client.set_user_context(org_id="123", user_id="456")

    # 自动根据文件大小来选择是流式上传还是断点续传
    file_info = await client.blobs.upload(
        "large_video.mp4",
        # mode=UploadMode.RESUMABLE # 也可以手动指定上传的模式
    )
```

#### 临时文件上传

```python
from file_hub_client import AsyncTamarFileHubClient, UploadMode

async with AsyncTamarFileHubClient() as client:
    # 设置用户上下文
    client.set_user_context(org_id="123", user_id="456")

    # 自动根据文件大小来选择是流式上传还是断点续传
    file_info = await client.blobs.upload(
        "large_video.mp4",
        # mode=UploadMode.RESUMABLE,  # 也可以手动指定上传的模式
        is_temporary=True,  # 由这个参数指定是否临时文件，是则不会纳入整个文件体系，即用户查询不到这个文件
        # expire_seconds, # 过期秒数，默认30天
    )
```

#### 保留原始文件名上传

```python
from file_hub_client import AsyncTamarFileHubClient

async with AsyncTamarFileHubClient() as client:
    # 设置用户上下文
    client.set_user_context(org_id="123", user_id="456")

    # 上传时保留原始文件名
    file_info = await client.blobs.upload(
        "document.pdf",
        keep_original_filename=True  # 保留原始文件名，默认为False
    )
    
    # 也可以指定文件夹和其他参数
    file_info = await client.blobs.upload(
        "report.xlsx",
        folder_id="folder-123",
        keep_original_filename=True,  # 保留原始文件名
        is_temporary=False
    )
```

### 文件下载

File Hub Client 提供了统一的下载接口，支持两种结构返回：

#### 下载返回结构

- **保存到本地（本地路径）**：适用于各种文件，直接下载到本地，分块流式下载，支持重试和断点续传
- **保存到内存（bytes）**：适用于小文件，直接下载到内存，分块流式下载，支持重试

#### 下载到内存（适用于小文件）

```python
from file_hub_client import AsyncTamarFileHubClient

async with AsyncTamarFileHubClient() as client:
    # 设置用户上下文
    client.set_user_context(org_id="123", user_id="456")

    # 下载文件到内存（适用于小文件）
    content = await client.blobs.download(file_id="file-001")
    print(f"下载完成，文件大小: {len(content)} bytes")
```

#### 下载到本地文件

```python
from file_hub_client import AsyncTamarFileHubClient
from pathlib import Path

async with AsyncTamarFileHubClient() as client:
    # 设置用户上下文
    client.set_user_context(org_id="123", user_id="456")

    # 下载文件到本地
    save_path = await client.blobs.download(
        file_id="file-001",
        save_path="downloads/document.pdf"  # 或 Path 对象
    )
    print(f"文件已保存到: {save_path}")
```

#### 高级下载功能

File Hub Client 提供了高级的下载URL管理功能，支持批量操作和直接获取GCS URL：

##### 批量生成下载URL

当需要为多个文件生成下载URL时，使用批量接口可以显著提高效率：

```python
from file_hub_client import AsyncTamarFileHubClient

async with AsyncTamarFileHubClient() as client:
    # 设置用户上下文
    client.set_user_context(org_id="123", user_id="456")
    
    # 批量生成下载URL
    file_ids = ["file-001", "file-002", "file-003"]
    
    result = await client.blobs.batch_generate_download_url(
        file_ids=file_ids,
        is_cdn=True,           # 使用CDN加速（可选，默认为True）
        expire_seconds=3600    # URL有效期1小时（可选）
    )
    
    # 处理结果
    for url_info in result.download_urls:
        if url_info.error:
            print(f"文件 {url_info.file_id} 生成URL失败: {url_info.error}")
        else:
            print(f"文件 {url_info.file_id}:")
            print(f"  下载URL: {url_info.url}")
            print(f"  MIME类型: {url_info.mime_type}")
            # 根据MIME类型处理文件
            if url_info.mime_type.startswith('image/'):
                print(f"  这是一个图片文件")
            elif url_info.mime_type == 'application/pdf':
                print(f"  这是一个PDF文件")
```

同步客户端示例：

```python
from file_hub_client import TamarFileHubClient

with TamarFileHubClient() as client:
    client.set_user_context(org_id="123", user_id="456")
    
    result = client.blobs.batch_generate_download_url(
        file_ids=["file-001", "file-002"],
        is_cdn=False  # 不使用CDN，直接返回源站URL
    )
```

**注意**：`batch_generate_download_url` 方法返回一个 `BatchDownloadUrlResponse` 对象，其中 `download_urls` 字段包含 `DownloadUrlInfo` 对象列表，每个对象包含：
- `file_id`: 文件ID
- `url`: 下载URL（如果成功生成）
- `mime_type`: 文件的MIME类型，便于正确处理文件内容
- `error`: 错误信息（如果生成失败）

##### 获取GCS URL

对于需要直接访问Google Cloud Storage的场景，可以获取文件的GCS URL和MIME类型信息：

```python
# 异步客户端 - 获取单个文件的GCS URL
async with AsyncTamarFileHubClient() as client:
    client.set_user_context(org_id="123", user_id="456")
    
    gcs_response = await client.blobs.get_gcs_url(file_id="file-001")
    print(f"GCS URL: {gcs_response.gcs_url}")
    print(f"MIME类型: {gcs_response.mime_type}")
    # 输出示例：
    # GCS URL: gs://bucket-name/path/to/file.pdf
    # MIME类型: application/pdf
```

**注意**：`get_gcs_url` 方法现在返回一个 `GetGcsUrlResponse` 对象，包含：
- `gcs_url`: Google Cloud Storage 的完整路径
- `mime_type`: 文件的MIME类型，便于正确处理文件内容

##### 批量获取GCS URL

批量获取多个文件的GCS URL和MIME类型信息：

```python
# 异步客户端 - 批量获取GCS URL
async with AsyncTamarFileHubClient() as client:
    client.set_user_context(org_id="123", user_id="456")
    
    file_ids = ["file-001", "file-002", "file-003"]
    result = await client.blobs.batch_get_gcs_url(file_ids)
    
    # 处理结果
    for url_info in result.gcs_urls:
        if url_info.error:
            print(f"文件 {url_info.file_id} 获取GCS URL失败: {url_info.error}")
        else:
            print(f"文件 {url_info.file_id}:")
            print(f"  GCS URL: {url_info.gcs_url}")
            print(f"  MIME类型: {url_info.mime_type}")
            # 根据MIME类型处理文件
            if url_info.mime_type.startswith('image/'):
                print(f"  这是一个图片文件")
            elif url_info.mime_type == 'application/pdf':
                print(f"  这是一个PDF文件")
```

同步客户端示例：

```python
# 同步客户端 - 批量获取GCS URL
with TamarFileHubClient() as client:
    client.set_user_context(org_id="123", user_id="456")
    
    # 获取单个GCS URL
    gcs_response = client.blobs.get_gcs_url(file_id="file-001")
    print(f"GCS URL: {gcs_response.gcs_url}")
    print(f"MIME类型: {gcs_response.mime_type}")
    
    # 批量获取GCS URL
    result = client.blobs.batch_get_gcs_url(["file-001", "file-002"])
```

##### 使用场景说明

1. **批量下载URL生成**：
   - 适用于需要同时下载多个文件的场景
   - 支持CDN加速，提供更好的下载体验
   - 可设置URL有效期，增强安全性
   - 批量操作减少网络往返，提高效率

2. **GCS URL获取**：
   - 适用于需要与Google Cloud服务集成的场景
   - 可用于数据分析、批处理等后端处理
   - 支持使用GCS工具进行文件操作
   - 返回的是永久有效的存储路径
   - 同时返回MIME类型信息，便于正确处理不同类型的文件
   - 可根据MIME类型选择合适的处理方式（如图片处理、文档解析等）

3. **错误处理**：
   - 每个文件独立处理，部分失败不影响其他文件
   - 错误信息通过 `error` 字段返回
   - 建议在批量操作时做好错误处理和重试逻辑

### 媒体文件压缩服务

File Hub Client 支持图片和视频文件的自动压缩处理，提供多种规格的压缩变体以满足不同使用场景的需求。

#### 获取文件压缩状态

```python
from file_hub_client import AsyncTamarFileHubClient

async with AsyncTamarFileHubClient() as client:
    client.set_user_context(org_id="123", user_id="456")
    
    # 获取文件压缩状态
    status = await client.blobs.get_compression_status(file_id="file-001")
    
    print(f"压缩状态: {status.status}")  # pending, processing, completed, failed
    if status.error_message:
        print(f"错误信息: {status.error_message}")
    
    # 查看可用的压缩变体
    for variant in status.variants:
        print(f"变体: {variant.variant_name}")
        print(f"  类型: {variant.variant_type}")  # image, video, thumbnail
        print(f"  尺寸: {variant.width}x{variant.height}")
        print(f"  大小: {variant.file_size} bytes")
        print(f"  格式: {variant.format}")
        print(f"  压缩比: {variant.compression_ratio:.2f}")
```

#### 获取压缩变体列表

```python
# 获取所有压缩变体
variants = await client.blobs.get_compressed_variants(file_id="file-001")

# 按类型过滤变体
image_variants = await client.blobs.get_compressed_variants(
    file_id="file-001",
    variant_type="image"  # image, video, thumbnail
)

# 处理变体信息
for variant in variants.variants:
    print(f"变体名称: {variant.variant_name}")  # large, medium, small, thumbnail
    print(f"媒体类型: {variant.media_type}")
    print(f"文件格式: {variant.format}")
    if variant.quality:
        print(f"质量: {variant.quality}")
    if variant.duration:
        print(f"时长: {variant.duration}秒")
    if variant.bitrate:
        print(f"比特率: {variant.bitrate}")
```

#### 下载压缩变体

```python
# 生成压缩变体的下载URL
variant_url = await client.blobs.generate_variant_download_url(
    file_id="file-001",
    variant_name="medium",  # large, medium, small, thumbnail
    expire_seconds=3600,    # URL有效期
    is_cdn=True            # 是否使用CDN
)

print(f"下载URL: {variant_url.url}")
if variant_url.error:
    print(f"生成URL错误: {variant_url.error}")

# 查看变体详细信息
if variant_url.variant_info:
    info = variant_url.variant_info
    print(f"变体信息:")
    print(f"  尺寸: {info.width}x{info.height}")
    print(f"  格式: {info.format}")
    print(f"  文件大小: {info.file_size} bytes")
```

#### 触发重新压缩

```python
# 触发文件重新压缩（当需要更新压缩设置时）
recompression = await client.blobs.trigger_recompression(
    file_id="file-001",
    force_reprocess=False  # 是否强制重新处理
)

print(f"任务ID: {recompression.task_id}")
print(f"状态: {recompression.status}")

# 监控压缩进度
import asyncio
while True:
    status = await client.blobs.get_compression_status(file_id="file-001")
    print(f"当前状态: {status.status}")
    
    if status.status in ["completed", "failed"]:
        break
    
    await asyncio.sleep(5)  # 等待5秒后再次检查
```

#### 同步客户端压缩服务

```python
from file_hub_client import TamarFileHubClient

with TamarFileHubClient() as client:
    client.set_user_context(org_id="123", user_id="456")
    
    # 所有压缩服务方法都有对应的同步版本
    status = client.blobs.get_compression_status(file_id="file-001")
    variants = client.blobs.get_compressed_variants(file_id="file-001")
    recompression = client.blobs.trigger_recompression(file_id="file-001")
    variant_url = client.blobs.generate_variant_download_url(
        file_id="file-001",
        variant_name="thumbnail"
    )
```

#### 压缩服务使用场景

1. **多设备适配**：
   - `large` - 高分辨率显示设备
   - `medium` - 标准桌面和平板
   - `small` - 手机端显示
   - `thumbnail` - 缩略图预览

2. **带宽优化**：
   - 根据网络状况选择合适的变体
   - 移动端使用压缩变体节省流量
   - 预览场景使用缩略图快速加载

3. **存储优化**：
   - 自动生成多种规格，无需手动处理
   - 智能压缩算法平衡质量和大小
   - 支持视频和图片的不同压缩策略

4. **性能优化**：
   - 异步压缩处理，不阻塞上传流程
   - 支持重新压缩以应用新的压缩设置
   - 批量状态查询减少网络请求

### 批量文件状态查询

File Hub Client 提供了高效的批量文件状态查询功能，可以一次性获取多个文件的上传、压缩、同步状态：

#### 基础批量查询

```python
from file_hub_client import AsyncTamarFileHubClient

async with AsyncTamarFileHubClient() as client:
    client.set_user_context(org_id="123", user_id="456")
    
    # 批量查询多个文件的状态
    file_ids = ["file-id-1", "file-id-2", "file-id-3"]
    response = await client.blobs.batch_get_file_status(
        file_ids=file_ids,
        include_details=False  # 是否包含详细信息，默认False
    )
    
    print(f"查询时间戳: {response.timestamp}")
    print(f"缓存命中数量: {response.cache_hit_count}")
    print(f"查询到 {len(response.statuses)} 个文件状态")
    
    for status in response.statuses:
        print(f"文件ID: {status.file_id}")
        print(f"  上传状态: {status.upload_status.value}")
        print(f"  压缩状态: {status.compression_status.value}")
        print(f"  同步状态: {status.sync_status.value}")
        
        if status.error_message:
            print(f"  错误信息: {status.error_message}")
```

#### 详细信息查询

```python
# 查询详细状态信息
detailed_response = await client.blobs.batch_get_file_status(
    file_ids=file_ids,
    include_details=True  # 包含详细信息
)

for status in detailed_response.statuses:
    print(f"文件ID: {status.file_id}")
    print(f"  上传状态: {status.upload_status.value}")
    print(f"  压缩状态: {status.compression_status.value}")
    print(f"  同步状态: {status.sync_status.value}")
    
    if status.details:
        print("  详细信息:")
        if status.details.file_size:
            print(f"    文件大小: {status.details.file_size} 字节")
        if status.details.storage_type:
            print(f"    存储类型: {status.details.storage_type}")
        if status.details.storage_region:
            print(f"    存储区域: {status.details.storage_region}")
            
        # 压缩相关详细信息
        if status.details.compression_task_id:
            print(f"    压缩任务ID: {status.details.compression_task_id}")
        if status.details.compression_variants_count is not None:
            print(f"    压缩变体数量: {status.details.compression_variants_count}")
        if status.details.compression_progress is not None:
            print(f"    压缩进度: {status.details.compression_progress * 100:.1f}%")
        
        # 同步相关详细信息
        if status.details.sync_regions_total is not None:
            print(f"    同步区域总数: {status.details.sync_regions_total}")
        if status.details.sync_regions_completed is not None:
            print(f"    已完成同步区域: {status.