lexlepty commited on
Commit
96aeb0c
·
verified ·
1 Parent(s): ee3129c

Upload 6 files

Browse files
Files changed (6) hide show
  1. .env.example +29 -0
  2. app.py +246 -0
  3. config.py +25 -0
  4. models.py +45 -0
  5. requirements.txt +5 -0
  6. utils.py +28 -0
.env.example ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # HuggingFace配置
2
+ HF_DATASET_ID=用户名/数据仓库
3
+ HF_TOKEN=上个人中心申请
4
+ HF_BRANCH=分支,基本上都是main
5
+ PROXY_DOMAIN=hf-mirror.com
6
+ #也可以自己反代,当然也可以直接使用这个国内镜像
7
+
8
+ # 系统认证配置
9
+ REQUIRE_LOGIN=true
10
+ #改成false就不用认证了,我猜写的时候这个逻辑我加了吧?也许?大概?
11
+ ACCESS_PASSWORD=
12
+ #这个就是默认的访问密码
13
+ SECRET_KEY=1234
14
+ #这个随便反正只是flask框架用的,Claude帮我加了反正
15
+
16
+ # MySQL数据库配置
17
+ MYSQL_HOST=mysql.sqlpub.com
18
+ #这个免费,反正就存个文件路径
19
+ MYSQL_PORT=3306
20
+ MYSQL_USER=mistpeak
21
+ #用户自己设置
22
+ MYSQL_PASSWORD=
23
+ #这个密码自己设置
24
+ MYSQL_DATABASE=datafile
25
+ #数据库名字随便起就行
26
+
27
+ # 可选系统配置,这俩其实没什么用,懒得删除了
28
+ ALLOWED_FILE_TYPES=jpg,jpeg,png,gif,mp4,pdf,doc,docx,zip,rar,txt
29
+ MAX_FILE_SIZE=1T
app.py ADDED
@@ -0,0 +1,246 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import (
2
+ Flask,
3
+ render_template,
4
+ request,
5
+ jsonify,
6
+ redirect,
7
+ url_for,
8
+ send_file
9
+ )
10
+ from io import BytesIO
11
+ import urllib.parse
12
+ from functools import wraps
13
+ import requests
14
+ import hashlib
15
+ import os
16
+ from config import Config
17
+ from models import Database
18
+ from utils import get_file_type, format_file_size
19
+ from huggingface_hub import HfApi
20
+
21
+ # 初始化 HuggingFace API
22
+ api = HfApi(token=Config.HF_TOKEN)
23
+ app = Flask(__name__)
24
+ app.config['SECRET_KEY'] = Config.SECRET_KEY
25
+ db = Database()
26
+
27
+ def require_auth(f):
28
+ @wraps(f)
29
+ def decorated(*args, **kwargs):
30
+ if not Config.REQUIRE_LOGIN:
31
+ return f(*args, **kwargs)
32
+ if not request.cookies.get('authenticated'):
33
+ if request.is_json:
34
+ return jsonify({'error': 'Unauthorized'}), 401
35
+ return redirect(url_for('login'))
36
+ return f(*args, **kwargs)
37
+ return decorated
38
+
39
+ @app.route('/login', methods=['GET', 'POST'])
40
+ def login():
41
+ if request.method == 'POST':
42
+ if request.form.get('password') == Config.ACCESS_PASSWORD:
43
+ response = jsonify({'success': True})
44
+ response.set_cookie('authenticated', 'true', secure=True, httponly=True)
45
+ return response
46
+ return jsonify({'error': 'Invalid password'}), 401
47
+ return render_template('login.html')
48
+
49
+ @app.route('/logout')
50
+ def logout():
51
+ response = redirect(url_for('login'))
52
+ response.delete_cookie('authenticated')
53
+ return response
54
+
55
+ @app.route('/')
56
+ @require_auth
57
+ def index():
58
+ return render_template('index.html')
59
+
60
+ @app.route('/api/files/list/')
61
+ @app.route('/api/files/list/<path:directory>')
62
+ @require_auth
63
+ def list_files(directory=''):
64
+ try:
65
+ url = f"https://huggingface.co/api/datasets/{Config.HF_DATASET_ID}/tree/{Config.HF_BRANCH}"
66
+ if directory:
67
+ url = f"{url}/{directory}"
68
+
69
+ response = requests.get(
70
+ url,
71
+ headers={'Authorization': f'Bearer {Config.HF_TOKEN}'}
72
+ )
73
+ if not response.ok:
74
+ return jsonify({'error': 'Failed to fetch files', 'details': response.text}), response.status_code
75
+
76
+ files = response.json()
77
+ for file in files:
78
+ if file['type'] == 'file':
79
+ file['file_type'] = get_file_type(file['path'])
80
+ file['size_formatted'] = format_file_size(file['size'])
81
+ # 添加预览和下载URL
82
+ file['preview_url'] = f"/api/files/preview/{file['path']}"
83
+ file['download_url'] = f"/api/files/download/{file['path']}"
84
+
85
+ return jsonify(files)
86
+ except Exception as e:
87
+ return jsonify({'error': str(e)}), 500
88
+
89
+ @app.route('/api/files/preview/<path:filepath>')
90
+ @require_auth
91
+ def preview_file(filepath):
92
+ try:
93
+ file_type = get_file_type(filepath)
94
+ if file_type not in ['image', 'video', 'document']:
95
+ return jsonify({'error': 'File type not supported for preview'}), 400
96
+
97
+ url = f"https://{Config.PROXY_DOMAIN}/datasets/{Config.HF_DATASET_ID}/resolve/{Config.HF_BRANCH}/{filepath}"
98
+ response = requests.get(
99
+ url,
100
+ headers={'Authorization': f'Bearer {Config.HF_TOKEN}'},
101
+ stream=True
102
+ )
103
+
104
+ if response.ok:
105
+ # 创建文件的内存缓存
106
+ file_data = BytesIO(response.content)
107
+
108
+ # 根据文件类型返回适当的响应
109
+ return send_file(
110
+ file_data,
111
+ mimetype=response.headers.get('content-type', 'application/octet-stream'),
112
+ conditional=True # 启用条件请求支持
113
+ )
114
+
115
+ return jsonify({'error': 'Failed to fetch file'}), response.status_code
116
+ except Exception as e:
117
+ return jsonify({'error': str(e)}), 500
118
+
119
+
120
+ @app.route('/api/files/download/<path:filepath>')
121
+ @require_auth
122
+ def download_file(filepath):
123
+ try:
124
+ url = f"https://{Config.PROXY_DOMAIN}/datasets/{Config.HF_DATASET_ID}/resolve/{Config.HF_BRANCH}/{filepath}"
125
+ response = requests.get(
126
+ url,
127
+ headers={'Authorization': f'Bearer {Config.HF_TOKEN}'},
128
+ stream=True
129
+ )
130
+
131
+ if response.ok:
132
+ # 创建内存文件对象
133
+ file_obj = BytesIO(response.content)
134
+
135
+ # 获取文件名并进行编码
136
+ filename = os.path.basename(filepath)
137
+ encoded_filename = urllib.parse.quote(filename.encode('utf-8'))
138
+
139
+ # 使用 send_file 返回文件
140
+ return send_file(
141
+ file_obj,
142
+ download_name=filename,
143
+ as_attachment=True,
144
+ mimetype=response.headers.get('content-type', 'application/octet-stream')
145
+ )
146
+
147
+ return jsonify({'error': 'File not found'}), 404
148
+ except Exception as e:
149
+ return jsonify({'error': str(e)}), 500
150
+
151
+ @app.route('/api/files/upload', methods=['POST'])
152
+ @require_auth
153
+ def upload_file():
154
+ if 'file' not in request.files:
155
+ return jsonify({'error': 'No file provided'}), 400
156
+
157
+ file = request.files['file']
158
+ current_path = request.form.get('path', '').strip('/')
159
+
160
+ try:
161
+ file_content = file.read()
162
+ file.seek(0)
163
+
164
+ original_name = file.filename
165
+ stored_name = original_name
166
+ full_path = os.path.join(current_path, stored_name).replace("\\", "/")
167
+
168
+ response = api.upload_file(
169
+ path_or_fileobj=file_content,
170
+ path_in_repo=full_path,
171
+ repo_id=Config.HF_DATASET_ID,
172
+ repo_type="dataset",
173
+ token=Config.HF_TOKEN
174
+ )
175
+
176
+ if response:
177
+ with db.conn.cursor() as cursor:
178
+ cursor.execute("""
179
+ INSERT INTO files (
180
+ original_name, stored_name, file_path,
181
+ file_type, file_size
182
+ ) VALUES (%s, %s, %s, %s, %s)
183
+ """, (
184
+ original_name,
185
+ stored_name,
186
+ full_path,
187
+ get_file_type(original_name),
188
+ len(file_content)
189
+ ))
190
+ db.conn.commit()
191
+
192
+ return jsonify({'success': True})
193
+
194
+ return jsonify({'error': 'Upload failed'}), 500
195
+
196
+ except Exception as e:
197
+ return jsonify({'error': str(e)}), 500
198
+
199
+ @app.route('/api/files/search')
200
+ @require_auth
201
+ def search_files():
202
+ keyword = request.args.get('keyword', '')
203
+ if not keyword:
204
+ return jsonify([])
205
+
206
+ try:
207
+ files = db.search_files(keyword)
208
+ return jsonify([{
209
+ 'name': f['original_name'],
210
+ 'path': f['file_path'],
211
+ 'type': get_file_type(f['file_path']),
212
+ 'size': format_file_size(f['file_size']),
213
+ 'created_at': f['created_at'].strftime('%Y-%m-%d %H:%M:%S')
214
+ } for f in files])
215
+ except Exception as e:
216
+ return jsonify({'error': str(e)}), 500
217
+
218
+ @app.route('/api/files/delete/<path:filepath>', methods=['DELETE'])
219
+ @require_auth
220
+ def delete_file(filepath):
221
+ try:
222
+ # Initialize HuggingFace API
223
+ api = HfApi(token=Config.HF_TOKEN)
224
+
225
+ # Delete file from HuggingFace Hub
226
+ api.delete_file(
227
+ path_in_repo=filepath,
228
+ repo_id=Config.HF_DATASET_ID,
229
+ repo_type="dataset"
230
+ )
231
+
232
+ # Delete file record from database
233
+ with db.conn.cursor() as cursor:
234
+ cursor.execute(
235
+ "DELETE FROM files WHERE file_path = %s",
236
+ [filepath]
237
+ )
238
+ db.conn.commit()
239
+
240
+ return jsonify({'success': True})
241
+
242
+ except Exception as e:
243
+ return jsonify({'error': str(e)}), 500
244
+
245
+ if __name__ == '__main__':
246
+ app.run(host='0.0.0.0', port=5000, debug=True)
config.py ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from dotenv import load_dotenv
3
+
4
+ load_dotenv()
5
+
6
+ class Config:
7
+ # HuggingFace配置
8
+ HF_DATASET_ID = os.getenv('HF_DATASET_ID')
9
+ HF_TOKEN = os.getenv('HF_TOKEN')
10
+ HF_BRANCH = os.getenv('HF_BRANCH', 'main')
11
+ PROXY_DOMAIN = os.getenv('PROXY_DOMAIN', 'huggingface.co')
12
+
13
+ # 系统配置
14
+ REQUIRE_LOGIN = os.getenv('REQUIRE_LOGIN', 'true').lower() == 'true'
15
+ ACCESS_PASSWORD = os.getenv('ACCESS_PASSWORD')
16
+ SECRET_KEY = os.getenv('SECRET_KEY')
17
+
18
+ # MySQL配置
19
+ MYSQL_CONFIG = {
20
+ 'host': os.getenv('MYSQL_HOST', 'localhost'),
21
+ 'port': int(os.getenv('MYSQL_PORT', 3306)),
22
+ 'user': os.getenv('MYSQL_USER'),
23
+ 'password': os.getenv('MYSQL_PASSWORD'),
24
+ 'database': os.getenv('MYSQL_DATABASE')
25
+ }
models.py ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pymysql
2
+ from config import Config
3
+
4
+ class Database:
5
+ def __init__(self):
6
+ self.conn = pymysql.connect(
7
+ host=Config.MYSQL_CONFIG['host'],
8
+ port=Config.MYSQL_CONFIG['port'],
9
+ user=Config.MYSQL_CONFIG['user'],
10
+ password=Config.MYSQL_CONFIG['password'],
11
+ charset='utf8mb4',
12
+ cursorclass=pymysql.cursors.DictCursor
13
+ )
14
+ self.init_db()
15
+
16
+ def init_db(self):
17
+ with self.conn.cursor() as cursor:
18
+ # 创建数据库
19
+ cursor.execute(f"CREATE DATABASE IF NOT EXISTS {Config.MYSQL_CONFIG['database']}")
20
+ cursor.execute(f"USE {Config.MYSQL_CONFIG['database']}")
21
+
22
+ # 创建文件表
23
+ cursor.execute("""
24
+ CREATE TABLE IF NOT EXISTS files (
25
+ id BIGINT PRIMARY KEY AUTO_INCREMENT,
26
+ original_name VARCHAR(255) NOT NULL,
27
+ stored_name VARCHAR(255) NOT NULL,
28
+ file_path VARCHAR(500) NOT NULL,
29
+ file_type VARCHAR(50),
30
+ file_size BIGINT,
31
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
32
+ UNIQUE KEY idx_path (file_path)
33
+ ) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci
34
+ """)
35
+ self.conn.commit()
36
+
37
+ def search_files(self, keyword):
38
+ with self.conn.cursor() as cursor:
39
+ cursor.execute("""
40
+ SELECT * FROM files
41
+ WHERE original_name LIKE %s
42
+ OR file_path LIKE %s
43
+ ORDER BY created_at DESC
44
+ """, (f"%{keyword}%", f"%{keyword}%"))
45
+ return cursor.fetchall()
requirements.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ Flask
2
+ python-dotenv
3
+ requests
4
+ PyMySQL
5
+ python-magic
utils.py ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+
3
+ def get_file_type(filename):
4
+ """根据文件扩展名判断文件类型"""
5
+ ext = os.path.splitext(filename)[1].lower()
6
+
7
+ # 文件类型映射
8
+ type_map = {
9
+ 'image': ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp'],
10
+ 'video': ['.mp4', '.avi', '.mov', '.wmv', '.flv', '.mkv'],
11
+ 'document': ['.pdf', '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx', '.txt'],
12
+ 'audio': ['.mp3', '.wav', '.ogg', '.m4a'],
13
+ 'archive': ['.zip', '.rar', '.7z', '.tar', '.gz'],
14
+ 'code': ['.py', '.js', '.html', '.css', '.json', '.xml']
15
+ }
16
+
17
+ for file_type, extensions in type_map.items():
18
+ if ext in extensions:
19
+ return file_type
20
+ return 'other'
21
+
22
+ def format_file_size(size):
23
+ """格式化文件大小"""
24
+ for unit in ['B', 'KB', 'MB', 'GB', 'TB']:
25
+ if size < 1024:
26
+ return f"{size:.2f} {unit}"
27
+ size /= 1024
28
+ return f"{size:.2f} PB"