File size: 7,159 Bytes
b6db694
 
 
27beae4
b6db694
 
 
 
 
 
 
 
 
 
 
27beae4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b6db694
 
 
 
 
27beae4
b6db694
 
 
 
 
 
 
 
 
 
 
 
27beae4
b6db694
 
 
 
 
27beae4
b6db694
 
27beae4
b6db694
 
 
27beae4
b6db694
 
 
 
 
 
 
 
 
 
 
27beae4
b6db694
 
 
27beae4
 
 
 
b6db694
 
27beae4
 
 
d0c18f0
b6db694
27beae4
b6db694
 
27beae4
b6db694
 
 
 
27beae4
b6db694
 
27beae4
 
b6db694
27beae4
b6db694
 
27beae4
b6db694
 
27beae4
b6db694
27beae4
 
 
 
 
b6db694
 
27beae4
b6db694
 
27beae4
b6db694
 
 
cc826a1
b6db694
 
27beae4
b6db694
27beae4
b6db694
 
 
 
 
 
 
 
 
27beae4
b6db694
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27beae4
b6db694
 
27beae4
b6db694
 
 
 
 
 
 
27beae4
b6db694
 
27beae4
b6db694
 
 
 
27beae4
b6db694
 
27beae4
b6db694
 
27beae4
b6db694
 
 
27beae4
b6db694
 
 
 
27beae4
b6db694
 
 
 
 
 
 
 
 
 
27beae4
b6db694
 
27beae4
b6db694
 
 
 
 
 
 
 
27beae4
b6db694
 
27beae4
b6db694
 
27beae4
 
b6db694
 
 
 
 
 
 
 
 
27beae4
b6db694
 
 
 
 
 
 
 
 
 
27beae4
b6db694
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
import os
import uuid
import asyncio
import logging
from datetime import datetime
from pathlib import Path
from fastapi import APIRouter, UploadFile, File, Form, HTTPException, BackgroundTasks
from fastapi.responses import FileResponse, JSONResponse

router = APIRouter()

# 全局变量,用于存储插件实例
plugin = None
cleanup_task_handle = None

logger = logging.getLogger(__name__)


def stop_cleanup_task():
    """停止清理任务"""
    global cleanup_task_handle
    if cleanup_task_handle and not cleanup_task_handle.done():
        cleanup_task_handle.cancel()
        cleanup_task_handle = None
        logger.info("文件清理定时任务已停止")


def start_cleanup_task():
    """启动清理任务"""
    global cleanup_task_handle
    if plugin and not cleanup_task_handle:
        try:
            loop = asyncio.get_event_loop()
            if loop.is_running():
                cleanup_task_handle = loop.create_task(periodic_cleanup())
                logger.info("文件清理定时任务已启动")
            else:
                logger.warning("事件循环未运行,无法启动定时任务")
        except RuntimeError as e:
            logger.warning(f"启动定时任务失败: {e}")


def set_plugin_instance(plugin_instance):
    """由系统调用,注入插件实例"""
    global plugin
    plugin = plugin_instance
    start_cleanup_task()


async def periodic_cleanup():
    """定期清理任务"""
    while True:
        try:
            await asyncio.sleep(3600)  # 每小时检查一次
            if plugin:
                await plugin.cleanup_expired_files()
        except asyncio.CancelledError:
            break
        except Exception as e:
            logger.error(f"清理任务出错: {e}")


@router.post("/upload")
async def upload_file(
    file: UploadFile = File(...),
    file_type: str = Form("temp"),  # "temp" 或 "permanent"
):
    """上传文件接口

    参数:
    - file: 上传的文件
    - file_type: 文件类型,"temp" 为临时文件(8小时后自动删除),"permanent" 为永久文件

    返回:
    {
        "file_id": "生成的文件ID",
        "filename": "文件名",
        "url": "临时访问URL",
        "type": "文件类型",
        "created": "创建时间"
    }
    """
    if not plugin:
        raise HTTPException(status_code=500, detail="插件未加载")

    try:
        # 验证文件类型
        if file_type not in ["temp", "permanent"]:
            raise HTTPException(
                status_code=400, detail="无效的文件类型,应为 'temp' 或 'permanent'"
            )

        # 允许的文件类型
        allowed_extensions = {
            "image": [".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp"],
            "video": [".mp4", ".avi", ".mov", ".mkv", ".flv", ".wmv", ".webm"],
            "audio": [".mp3", ".wav", ".aac", ".flac", ".ogg", ".m4a"],
            "document": [".md", ".txt", ".pdf", ".doc", ".docx"],
        }

        file_ext = Path(file.filename).suffix.lower()
        file_category = None

        for category, extensions in allowed_extensions.items():
            if file_ext in extensions:
                file_category = category
                break

        if not file_category:
            raise HTTPException(
                status_code=400,
                detail=f"不支持的文件格式: {file_ext}。支持的格式: {', '.join(sum(allowed_extensions.values(), []))}",
            )

        # 生成文件ID
        file_id = str(uuid.uuid4())

        # 读取文件内容
        content = await file.read()

        # 保存文件
        is_temp = file_type == "temp"
        success = await plugin.save_file(
            file_id, content, file.filename, is_temp=is_temp
        )

        if not success:
            raise HTTPException(status_code=500, detail="保存文件失败")

        # 获取元数据
        metadata = plugin.file_metadata[file_id]

        return {
            "file_id": file_id,
            "filename": file.filename,
            "url": f"/plugins/cache/api/file/{file_id}",
            "type": file_type,
            "created": datetime.fromtimestamp(metadata["created"]).isoformat(),
            "message": "文件上传成功",
        }

    except HTTPException:
        raise
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"上传文件时出错: {str(e)}")


@router.get("/list")
async def list_files():
    """获取文件列表接口

    返回:
    {
        "files": [
            {
                "file_id": "文件ID",
                "name": "文件名",
                "type": "temp/permanent",
                "created": "创建时间",
                "url": "临时访问URL"
            }
        ],
        "total": 文件总数
    }
    """
    if not plugin:
        raise HTTPException(status_code=500, detail="插件未加载")

    try:
        file_list = plugin.get_file_list()
        return {"files": file_list, "total": len(file_list)}
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"获取文件列表失败: {str(e)}")


@router.get("/file/{file_id}")
async def download_file(file_id: str):
    """下载文件接口

    参数:
    - file_id: 文件ID

    返回:文件内容
    """
    if not plugin:
        raise HTTPException(status_code=500, detail="插件未加载")

    try:
        file_path = plugin.get_file_path(file_id)

        if not file_path:
            raise HTTPException(status_code=404, detail="文件不存在")

        file_path_obj = Path(file_path)
        if not file_path_obj.exists():
            raise HTTPException(status_code=404, detail="文件不存在")

        # 返回文件
        return FileResponse(
            path=file_path_obj,
            filename=Path(file_path).name,
            media_type="application/octet-stream",
        )
    except HTTPException:
        raise
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"下载文件失败: {str(e)}")


@router.delete("/file/{file_id}")
async def delete_file(file_id: str):
    """删除文件接口

    参数:
    - file_id: 文件ID

    返回:
    {
        "success": true/false,
        "message": "删除结果消息"
    }
    """
    if not plugin:
        raise HTTPException(status_code=500, detail="插件未加载")

    try:
        success = await plugin.delete_file(file_id)

        if not success:
            raise HTTPException(status_code=404, detail="文件不存在或已删除")

        return {"success": True, "message": "文件删除成功"}
    except HTTPException:
        raise
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"删除文件失败: {str(e)}")


@router.get("/status")
async def get_status():
    """获取插件状态接口

    返回:
    {
        "name": "插件名称",
        "enabled": true/false,
        "file_count": 文件总数,
        "cache_dir": "缓存目录路径"
    }
    """
    if not plugin:
        return {"error": "插件未加载"}

    return plugin.get_status()