chatyou commited on
Commit
d454d67
·
verified ·
1 Parent(s): 545f5d9

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +211 -0
app.py ADDED
@@ -0,0 +1,211 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from flask import Flask, request, jsonify, send_file
3
+ from wsgidav.wsgidav_app import WsgiDAVApp
4
+ from wsgidav.dc.simple_dc import SimpleDomainController
5
+ from hf_bucket_provider import HfBucketProvider
6
+
7
+ # 从环境变量读取 Hugging Face Token
8
+ HF_TOKEN = os.environ.get("HF_TOKEN")
9
+ if not HF_TOKEN:
10
+ raise ValueError("HF_TOKEN environment variable not set. Please add it to Space Secrets.")
11
+
12
+ BUCKET_ID = "nagose/filebed" # 可改为你的 bucket
13
+
14
+ # 创建 Flask 应用
15
+ app = Flask(__name__)
16
+
17
+ # 创建 WebDAV 配置
18
+ dav_config = {
19
+ "provider_mapping": {
20
+ "/webdav": HfBucketProvider(BUCKET_ID, HF_TOKEN)
21
+ },
22
+ "http_authenticator": {
23
+ "domain_controller": SimpleDomainController(), # 允许匿名访问(或可通过 Basic Auth 配置)
24
+ "accept_basic": True,
25
+ "accept_digest": False,
26
+ "default_to_digest": False,
27
+ },
28
+ "simple_dc": {
29
+ "user_mapping": {"*": True} # 所有用户匿名访问(如果需要认证,可配置具体用户)
30
+ },
31
+ "verbose": 1, # 日志级别
32
+ }
33
+
34
+ # 创建 WsgiDAV 应用
35
+ dav_app = WsgiDAVApp(dav_config)
36
+
37
+ # 将 WsgiDAV 挂载到 Flask 的 /webdav 路径
38
+ @app.route('/webdav', defaults={'path': ''})
39
+ @app.route('/webdav/<path:path>', methods=['GET', 'POST', 'PUT', 'DELETE', 'HEAD', 'OPTIONS', 'PROPFIND', 'PROPPATCH', 'MKCOL', 'COPY', 'MOVE', 'LOCK', 'UNLOCK'])
40
+ def webdav_handler(path):
41
+ # 构建新的 PATH_INFO 和 SCRIPT_NAME
42
+ environ = request.environ
43
+ environ['PATH_INFO'] = '/' + path
44
+ environ['SCRIPT_NAME'] = '/webdav'
45
+ # 调用 wsgidav 应用
46
+ return dav_app(environ, lambda status, headers, exc_info=None: None)
47
+
48
+ # 原有的 Flask 管理界面(可从之前的版本继承)
49
+ HTML_CONTENT = """<!DOCTYPE html>
50
+ <html>
51
+ <head>
52
+ <meta charset="UTF-8">
53
+ <title>HF Bucket 管理器</title>
54
+ <style>
55
+ body { font-family: sans-serif; max-width: 800px; margin: 20px auto; }
56
+ .log { background: #1e293b; color: #b9e6f0; padding: 12px; border-radius: 8px; max-height: 300px; overflow: auto; }
57
+ .file-item { display: flex; justify-content: space-between; padding: 10px; background: #f8fafc; margin-bottom: 8px; }
58
+ button { padding: 8px 16px; background: #2563eb; color: white; border: none; border-radius: 4px; cursor: pointer; }
59
+ button.danger { background: #dc2626; }
60
+ input[type="file"] { margin: 10px 0; }
61
+ </style>
62
+ </head>
63
+ <body>
64
+ <h1>HF Bucket 管理器</h1>
65
+ <p>WebDAV 服务已挂载在 <code>/webdav</code> 路径,您可以使用任何 WebDAV 客户端连接。</p>
66
+ <p>例如:<code>https://你的-space地址/webdav</code></p>
67
+ <hr>
68
+ <h2>简单文件上传测试(使用原有API)</h2>
69
+ <input type="file" id="fileInput">
70
+ <button id="uploadBtn">上传</button>
71
+ <div class="log" id="log"></div>
72
+ <h2>文件列表</h2>
73
+ <div id="fileList"></div>
74
+
75
+ <script>
76
+ const fileInput = document.getElementById('fileInput');
77
+ const uploadBtn = document.getElementById('uploadBtn');
78
+ const logDiv = document.getElementById('log');
79
+ const fileListDiv = document.getElementById('fileList');
80
+
81
+ function addLog(msg, isErr = false) {
82
+ const p = document.createElement('p');
83
+ p.textContent = `[${new Date().toLocaleTimeString()}] ${isErr ? '❌ ' : ''}${msg}`;
84
+ if (isErr) p.style.color = '#f87171';
85
+ logDiv.appendChild(p);
86
+ logDiv.scrollTop = logDiv.scrollHeight;
87
+ }
88
+
89
+ async function loadList() {
90
+ try {
91
+ const res = await fetch('/list');
92
+ const files = await res.json();
93
+ if (files.length === 0) fileListDiv.innerHTML = '<p>暂无文件</p>';
94
+ else {
95
+ fileListDiv.innerHTML = files.map(f => `
96
+ <div class="file-item">
97
+ <a href="/file/${encodeURIComponent(f)}" target="_blank">${f}</a>
98
+ <button class="danger" onclick="deleteFile('${f.replace(/'/g, "\\\\'")}')">删除</button>
99
+ </div>
100
+ `).join('');
101
+ }
102
+ } catch (err) {
103
+ fileListDiv.innerHTML = '<p>加载失败</p>';
104
+ }
105
+ }
106
+
107
+ window.deleteFile = async function(filename) {
108
+ if (!confirm('确定删除?')) return;
109
+ await fetch('/delete/' + encodeURIComponent(filename), { method: 'DELETE' });
110
+ loadList();
111
+ };
112
+
113
+ uploadBtn.addEventListener('click', async () => {
114
+ const file = fileInput.files[0];
115
+ if (!file) return alert('请选择文件');
116
+ addLog('开始上传: ' + file.name);
117
+ uploadBtn.disabled = true;
118
+ const formData = new FormData();
119
+ formData.append('file', file);
120
+ try {
121
+ const res = await fetch('/upload', { method: 'POST', body: formData });
122
+ const data = await res.json();
123
+ if (res.ok) {
124
+ addLog('✅ 上传成功');
125
+ fileInput.value = '';
126
+ loadList();
127
+ } else {
128
+ addLog('❌ ' + data.error, true);
129
+ }
130
+ } catch (err) {
131
+ addLog('❌ ' + err.message, true);
132
+ } finally {
133
+ uploadBtn.disabled = false;
134
+ }
135
+ });
136
+
137
+ loadList();
138
+ </script>
139
+ </body>
140
+ </html>"""
141
+
142
+ @app.route('/')
143
+ def index():
144
+ return HTML_CONTENT
145
+
146
+ @app.route('/upload', methods=['POST'])
147
+ def upload():
148
+ # 复用之前的 upload 逻辑(可选)
149
+ # 这里为了简洁,直接调用 batch_bucket_files
150
+ from huggingface_hub import batch_bucket_files
151
+ if 'file' not in request.files:
152
+ return jsonify({'error': 'No file part'}), 400
153
+ file = request.files['file']
154
+ if file.filename == '':
155
+ return jsonify({'error': 'No selected file'}), 400
156
+ filename = file.filename
157
+ with tempfile.NamedTemporaryFile(delete=False) as tmp:
158
+ file.save(tmp.name)
159
+ try:
160
+ batch_bucket_files(
161
+ bucket_id=BUCKET_ID,
162
+ add=[(tmp.name, filename)],
163
+ token=HF_TOKEN
164
+ )
165
+ except Exception as e:
166
+ return jsonify({'error': str(e)}), 500
167
+ finally:
168
+ os.unlink(tmp.name)
169
+ return jsonify({'success': True, 'filename': filename})
170
+
171
+ @app.route('/list')
172
+ def list_files():
173
+ from huggingface_hub import list_bucket_tree
174
+ try:
175
+ items = list_bucket_tree(BUCKET_ID, recursive=True, token=HF_TOKEN)
176
+ files = [item.path for item in items if item.type == 'file']
177
+ return jsonify(files)
178
+ except Exception as e:
179
+ return jsonify({'error': str(e)}), 500
180
+
181
+ @app.route('/file/<path:filename>')
182
+ def get_file(filename):
183
+ from huggingface_hub import download_bucket_files
184
+ import tempfile
185
+ try:
186
+ with tempfile.TemporaryDirectory() as tmpdir:
187
+ local_path = os.path.join(tmpdir, filename)
188
+ download_bucket_files(
189
+ bucket_id=BUCKET_ID,
190
+ files=[(filename, local_path)],
191
+ token=HF_TOKEN
192
+ )
193
+ return send_file(local_path, as_attachment=True, download_name=filename)
194
+ except Exception as e:
195
+ return jsonify({'error': str(e)}), 500
196
+
197
+ @app.route('/delete/<path:filename>', methods=['DELETE'])
198
+ def delete_file(filename):
199
+ from huggingface_hub import batch_bucket_files
200
+ try:
201
+ batch_bucket_files(
202
+ bucket_id=BUCKET_ID,
203
+ delete=[filename],
204
+ token=HF_TOKEN
205
+ )
206
+ return jsonify({'success': True})
207
+ except Exception as e:
208
+ return jsonify({'error': str(e)}), 500
209
+
210
+ if __name__ == '__main__':
211
+ app.run(host='0.0.0.0', port=7860)