Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -1,231 +1,56 @@
|
|
1 |
import streamlit as st
|
2 |
import libtorrent as lt
|
3 |
import os
|
4 |
-
import
|
5 |
-
import
|
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 |
-
def safe_init_session():
|
58 |
-
"""线程安全的session_state初始化"""
|
59 |
-
with session_guard():
|
60 |
-
if 'download' not in st.session_state:
|
61 |
-
state = DownloadState()
|
62 |
-
st.session_state.download = {
|
63 |
-
'progress': state.progress,
|
64 |
-
'is_downloading': state.is_downloading,
|
65 |
-
'complete': state.complete,
|
66 |
-
'file_path': state.file_path,
|
67 |
-
'status': state.status,
|
68 |
-
'info_hash': state.info_hash
|
69 |
-
}
|
70 |
-
logging.info("Session state initialized")
|
71 |
-
|
72 |
-
def safe_sync_session():
|
73 |
-
"""双重保障的状态同步机制"""
|
74 |
-
safe_init_session() # 同步前确保初始化
|
75 |
-
state = DownloadState()
|
76 |
-
with session_guard():
|
77 |
-
st.session_state.download.update({
|
78 |
-
'progress': state.progress,
|
79 |
-
'is_downloading': state.is_downloading,
|
80 |
-
'complete': state.complete,
|
81 |
-
'file_path': state.file_path,
|
82 |
-
'status': state.status,
|
83 |
-
'info_hash': state.info_hash
|
84 |
-
})
|
85 |
-
|
86 |
-
# 初始化session状态(主线程保障)
|
87 |
-
safe_init_session()
|
88 |
-
|
89 |
-
def download_worker(magnet_link, save_path):
|
90 |
-
try:
|
91 |
-
state = DownloadState()
|
92 |
-
ts = TorrentSession()
|
93 |
-
|
94 |
-
# 重置状态
|
95 |
-
with session_guard():
|
96 |
-
state.reset()
|
97 |
-
safe_sync_session()
|
98 |
-
|
99 |
-
# 使用新版API添加磁力链接
|
100 |
-
params = {
|
101 |
-
'save_path': save_path,
|
102 |
-
'storage_mode': lt.storage_mode_t.storage_mode_sparse,
|
103 |
-
'flags': lt.torrent_flags.duplicate_is_error | lt.torrent_flags.auto_managed
|
104 |
-
}
|
105 |
-
|
106 |
-
handle = lt.add_magnet_uri(ts.ses, magnet_link, params)
|
107 |
-
state.info_hash = str(handle.info_hash())
|
108 |
-
safe_sync_session()
|
109 |
-
|
110 |
-
# 事件驱动等待元数据
|
111 |
-
state.status = "等待元数据..."
|
112 |
-
safe_sync_session()
|
113 |
-
|
114 |
-
metadata_received = False
|
115 |
-
start_time = time.time()
|
116 |
-
while not metadata_received:
|
117 |
-
ts.process_alerts()
|
118 |
-
for alert in ts.alerts:
|
119 |
-
if isinstance(alert, lt.metadata_received_alert):
|
120 |
-
if alert.handle == handle:
|
121 |
-
metadata_received = True
|
122 |
-
break
|
123 |
-
if time.time() - start_time > 300: # 5分钟超时
|
124 |
-
raise TimeoutError("获取元数据超时")
|
125 |
-
time.sleep(0.5)
|
126 |
-
|
127 |
-
# 获取文件信息
|
128 |
-
ti = handle.get_torrent_info()
|
129 |
-
state.status = f"开始下载 {ti.name()}"
|
130 |
-
safe_sync_session()
|
131 |
-
|
132 |
-
# 启动下载
|
133 |
-
handle.set_sequential_download(True)
|
134 |
-
handle.resume()
|
135 |
-
|
136 |
-
# 下载进度监控
|
137 |
-
state.is_downloading = True
|
138 |
-
safe_sync_session()
|
139 |
-
|
140 |
-
while not handle.status().is_seeding:
|
141 |
-
status = handle.status()
|
142 |
-
state.progress = status.progress * 100
|
143 |
-
|
144 |
-
# 状态更新
|
145 |
-
if status.state == lt.torrent_status.downloading_metadata:
|
146 |
-
state.status = "获取元数据..."
|
147 |
-
elif status.state == lt.torrent_status.downloading:
|
148 |
-
dl = status.download_rate / 1000
|
149 |
-
up = status.upload_rate / 1000
|
150 |
-
peers = status.num_peers
|
151 |
-
state.status = f"下载中: {dl:.1f}kB/s ↑{up:.1f}kB/s ↔{peers} peers"
|
152 |
-
|
153 |
-
safe_sync_session()
|
154 |
-
time.sleep(1)
|
155 |
-
|
156 |
-
# 下载完成
|
157 |
-
state.is_downloading = False
|
158 |
-
state.complete = True
|
159 |
-
state.file_path = save_path
|
160 |
-
state.status = "下载完成"
|
161 |
-
safe_sync_session()
|
162 |
-
|
163 |
-
except Exception as e:
|
164 |
-
logging.error(f"下载错误: {str(e)}")
|
165 |
-
state.status = f"错误: {str(e)}"
|
166 |
-
state.is_downloading = False
|
167 |
-
safe_sync_session()
|
168 |
-
finally:
|
169 |
-
safe_sync_session()
|
170 |
-
|
171 |
-
# Streamlit界面
|
172 |
-
st.title("🚀 磁力链接下载器")
|
173 |
-
|
174 |
-
with st.form("magnet_form"):
|
175 |
-
magnet = st.text_input("磁力链接", placeholder="magnet:?xt=urn:btih:...")
|
176 |
-
submitted = st.form_submit_button("开始下载",
|
177 |
-
disabled=st.session_state.download['is_downloading'])
|
178 |
-
|
179 |
-
if submitted:
|
180 |
-
if not magnet.startswith("magnet:"):
|
181 |
-
st.error("无效的磁力链接格式")
|
182 |
-
else:
|
183 |
-
save_dir = "./downloads"
|
184 |
-
os.makedirs(save_dir, exist_ok=True)
|
185 |
-
|
186 |
-
# 启动前同步状态
|
187 |
-
DownloadState().reset()
|
188 |
-
safe_sync_session()
|
189 |
-
|
190 |
-
threading.Thread(
|
191 |
-
target=download_worker,
|
192 |
-
args=(magnet, save_dir),
|
193 |
-
daemon=True
|
194 |
-
).start()
|
195 |
-
|
196 |
-
# 实时状态显示
|
197 |
-
safe_sync_session()
|
198 |
-
|
199 |
-
if st.session_state.download['is_downloading']:
|
200 |
-
cols = st.columns([1,3])
|
201 |
-
with cols[0]:
|
202 |
-
st.metric("进度", f"{st.session_state.download['progress']:.1f}%")
|
203 |
-
with cols[1]:
|
204 |
-
st.progress(st.session_state.download['progress']/100)
|
205 |
-
st.info(st.session_state.download['status'])
|
206 |
-
|
207 |
-
if st.session_state.download['complete']:
|
208 |
-
st.success("下载完成!")
|
209 |
-
files = [f for f in os.listdir(st.session_state.download['file_path'])
|
210 |
-
if os.path.isfile(os.path.join(st.session_state.download['file_path'], f))]
|
211 |
-
|
212 |
-
if files:
|
213 |
-
with st.expander("下载文件"):
|
214 |
-
selected = st.selectbox("选择文件", files)
|
215 |
-
with open(os.path.join(st.session_state.download['file_path'], selected), "rb") as f:
|
216 |
-
st.download_button(
|
217 |
-
"下载文件",
|
218 |
-
f,
|
219 |
-
file_name=selected,
|
220 |
-
mime="application/octet-stream"
|
221 |
-
)
|
222 |
-
|
223 |
-
# 状态监控线程
|
224 |
-
if 'monitor' not in st.session_state:
|
225 |
-
def status_monitor():
|
226 |
-
while True:
|
227 |
-
safe_sync_session()
|
228 |
-
time.sleep(0.5)
|
229 |
-
|
230 |
-
threading.Thread(target=status_monitor, daemon=True).start()
|
231 |
-
st.session_state.monitor = True
|
|
|
1 |
import streamlit as st
|
2 |
import libtorrent as lt
|
3 |
import os
|
4 |
+
from tqdm import tqdm
|
5 |
+
import shutil
|
6 |
+
|
7 |
+
def download_magnet(magnet_uri, download_path):
|
8 |
+
# 创建会话
|
9 |
+
ses = lt.session()
|
10 |
+
ses.listen_on(6881, 6891)
|
11 |
+
# 添加磁力链接
|
12 |
+
params = {
|
13 |
+
'save_path': download_path,
|
14 |
+
'storage_mode': lt.storage_mode_t(2)
|
15 |
+
}
|
16 |
+
handle = lt.add_magnet_uri(ses, magnet_uri, params)
|
17 |
+
st.write(f"正在下载:{handle.name()}")
|
18 |
+
# 等待元数据下载完成
|
19 |
+
while not handle.has_metadata():
|
20 |
+
pass
|
21 |
+
# 获取文件信息
|
22 |
+
file_info = handle.get_torrent_info()
|
23 |
+
total_size = file_info.total_size()
|
24 |
+
# 创建进度条
|
25 |
+
progress_bar = st.progress(0)
|
26 |
+
# 下载文件
|
27 |
+
for i in tqdm(range(100)):
|
28 |
+
s = handle.status()
|
29 |
+
progress = s.total_done / total_size
|
30 |
+
progress_bar.progress(progress)
|
31 |
+
if s.state == lt.torrent_status.seeding:
|
32 |
+
break
|
33 |
+
# 下载完成,返回文件路径
|
34 |
+
return os.path.join(download_path, handle.name())
|
35 |
+
|
36 |
+
def main():
|
37 |
+
st.title("磁力链接下载器")
|
38 |
+
magnet_uri = st.text_input("请输入磁力链接:")
|
39 |
+
if magnet_uri:
|
40 |
+
download_path = os.path.join(os.getcwd(), "downloads")
|
41 |
+
if not os.path.exists(download_path):
|
42 |
+
os.makedirs(download_path)
|
43 |
+
file_path = download_magnet(magnet_uri, download_path)
|
44 |
+
st.write(f"下载完成:{file_path}")
|
45 |
+
with open(file_path, "rb") as f:
|
46 |
+
st.download_button(
|
47 |
+
label="下载文件",
|
48 |
+
data=f,
|
49 |
+
file_name=os.path.basename(file_path),
|
50 |
+
mime="application/octet-stream"
|
51 |
+
)
|
52 |
+
# 下载完成后,删除文件
|
53 |
+
os.remove(file_path)
|
54 |
+
|
55 |
+
if __name__ == "__main__":
|
56 |
+
main()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|