1
- from fastapi import FastAPI , Request , HTTPException , UploadFile , File , Form
2
- from fastapi .responses import JSONResponse , FileResponse , HTMLResponse
3
- from fastapi .staticfiles import StaticFiles
1
+ from fastapi import FastAPI
4
2
from pathlib import Path
5
- import mimetypes
6
- import shutil
7
3
import os
4
+ from .routes .api import router as api_router
5
+ from .routes .page import router as page_router , init_static_files
8
6
9
7
app = FastAPI (title = "File Browser" )
10
8
11
9
# 从环境变量或默认值获取根目录
12
10
ROOT_DIR = Path (os .getenv ("FILE_SERVER_ROOT" , "./files" ))
13
11
ROOT_DIR .mkdir (parents = True , exist_ok = True )
14
12
15
- # 获取当前模块的路径
16
- PACKAGE_DIR = Path ( __file__ ). parent
13
+ # 设置 ROOT_DIR 到 app.state 中,以便在路由中使用
14
+ app . state . ROOT_DIR = ROOT_DIR
17
15
18
- # API routes
19
- @app .get ("/api/files" )
20
- async def list_files (path : str = "" ):
21
- """List files and directories at the given path"""
22
- try :
23
- target_path = ROOT_DIR / path
24
- if not target_path .exists ():
25
- return {"error" : "Path not found" }
26
-
27
- items = []
28
- for item in target_path .iterdir ():
29
- try :
30
- is_dir = item .is_dir ()
31
- item_info = {
32
- "name" : item .name ,
33
- "path" : str (item .relative_to (ROOT_DIR )),
34
- "type" : "directory" if is_dir else "file" ,
35
- "size" : 0 if is_dir else item .stat ().st_size ,
36
- "download_url" : f"/download/{ item .relative_to (ROOT_DIR )} "
37
- }
38
- items .append (item_info )
39
- except Exception :
40
- continue
16
+ # 初始化静态文件服务
17
+ init_static_files (app )
41
18
42
- # 按目录优先、名字升序排序
43
- items .sort (key = lambda x : (x ["type" ] != "directory" , x ["name" ].lower ()))
44
-
45
- return {
46
- "items" : items ,
47
- "current_path" : path
48
- }
49
- except Exception as e :
50
- return {"error" : str (e )}
51
-
52
- @app .get ("/download/{file_path:path}" )
53
- async def download_file (file_path : str ):
54
- """Download file"""
55
- try :
56
- file_path = ROOT_DIR / file_path
57
- if not file_path .exists () or not file_path .is_file ():
58
- return {"error" : "File not found" }
59
-
60
- return FileResponse (
61
- file_path ,
62
- filename = file_path .name ,
63
- media_type = "application/octet-stream"
64
- )
65
- except Exception as e :
66
- return {"error" : str (e )}
67
-
68
- @app .put ("/api/files/{file_path:path}" )
69
- async def update_file (file_path : str , request : Request ):
70
- """Update file content"""
71
- try :
72
- data = await request .json ()
73
- content = data .get ("content" , "" )
74
-
75
- file_path = ROOT_DIR / file_path
76
- if not file_path .exists ():
77
- return {"error" : "File not found" }
78
-
79
- file_path .write_text (content )
80
- return {"message" : "File updated successfully" }
81
- except Exception as e :
82
- return {"error" : str (e )}
83
-
84
- @app .delete ("/api/files/{file_path:path}" )
85
- async def delete_file (file_path : str ):
86
- """Delete file or directory"""
87
- try :
88
- target_path = ROOT_DIR / file_path
89
- if not target_path .exists ():
90
- return {"error" : "File or directory not found" }
91
-
92
- if target_path .is_file ():
93
- target_path .unlink ()
94
- else :
95
- import shutil
96
- shutil .rmtree (target_path )
97
-
98
- return {"message" : "Deleted successfully" }
99
- except Exception as e :
100
- return {"error" : str (e )}
101
-
102
- @app .post ("/api/upload/{path:path}" )
103
- async def upload_files (path : str , files : list [UploadFile ] = File (...), paths : list [str ] = Form (...)):
104
- """Upload files to the specified path"""
105
- try :
106
- target_path = ROOT_DIR / path
107
-
108
- uploaded_files = []
109
- for file , relative_path in zip (files , paths ):
110
- try :
111
- # 将 Windows 路径分隔符转换为 POSIX 格式
112
- relative_path = relative_path .replace ("\\ " , "/" )
113
-
114
- # 构建完整的目标路径
115
- file_path = target_path / relative_path
116
-
117
- # 确保父目录存在
118
- file_path .parent .mkdir (parents = True , exist_ok = True )
119
-
120
- # 如果文件已存在,添加数字后缀
121
- original_path = file_path
122
- counter = 1
123
- while file_path .exists ():
124
- stem = original_path .stem
125
- suffix = original_path .suffix
126
- file_path = original_path .parent / f"{ stem } _{ counter } { suffix } "
127
- counter += 1
128
-
129
- # 保存文件
130
- with open (file_path , "wb" ) as f :
131
- shutil .copyfileobj (file .file , f )
132
-
133
- uploaded_files .append ({
134
- "name" : file_path .name ,
135
- "path" : str (file_path .relative_to (ROOT_DIR )),
136
- "size" : file_path .stat ().st_size ,
137
- "download_url" : f"/download/{ file_path .relative_to (ROOT_DIR )} "
138
- })
139
- except Exception as e :
140
- raise HTTPException (status_code = 500 , detail = f"Failed to upload { relative_path } : { str (e )} " )
141
-
142
- return {"files" : uploaded_files }
143
- except Exception as e :
144
- raise HTTPException (status_code = 500 , detail = str (e ))
145
-
146
- # Mount static files for direct access to static assets
147
- app .mount ("/static" , StaticFiles (directory = PACKAGE_DIR / "static" ), name = "static" )
148
-
149
- # Serve index.html for the root path
150
- @app .get ("/" , response_class = HTMLResponse )
151
- async def serve_root ():
152
- """Serve index.html"""
153
- return (PACKAGE_DIR / "static/index.html" ).read_text (encoding = "utf-8" )
154
-
155
- # Redirect /blob/{path} to index.html for client-side routing
156
- @app .get ("/blob/{path:path}" , response_class = HTMLResponse )
157
- async def serve_blob_path (path : str ):
158
- """Serve index.html for blob paths"""
159
- return (PACKAGE_DIR / "static/index.html" ).read_text (encoding = "utf-8" )
160
-
161
- @app .get ("/raw/{file_path:path}" )
162
- async def get_raw_file (file_path : str ):
163
- """Get raw file content"""
164
- try :
165
- file_path = ROOT_DIR / file_path
166
- if not file_path .exists () or not file_path .is_file ():
167
- raise HTTPException (status_code = 404 , detail = "File not found" )
168
-
169
- # 获取文件的 MIME 类型
170
- mime_type , _ = mimetypes .guess_type (str (file_path ))
171
- if mime_type is None :
172
- mime_type = "application/octet-stream"
173
-
174
- return FileResponse (
175
- path = file_path ,
176
- media_type = mime_type ,
177
- filename = file_path .name
178
- )
179
- except Exception as e :
180
- raise HTTPException (status_code = 500 , detail = str (e ))
19
+ # 包含路由
20
+ app .include_router (api_router )
21
+ app .include_router (page_router )
0 commit comments