1use crate::{
2 core::{App, start_new_entry},
3 os::wakeup_window,
4 ui::{EntryData, MainWindow},
5 utils::LogErr,
6};
7use axum::{Json, Router, extract::State, http::StatusCode, routing::post};
8use crossfire::{MTx, mpsc};
9use serde::{Deserialize, Serialize};
10use slint::{VecModel, Weak};
11use std::{rc::Rc, time::Duration};
12use tracing::{error, info};
13use url::Url;
14
15#[derive(Debug, Clone, Serialize, Deserialize)]
16#[serde(rename_all = "camelCase")]
17pub struct DownloadOptions {
18 url: Url,
19 headers: Option<String>,
20}
21
22async fn download(
23 State(tx): State<MTx<mpsc::List<DownloadOptions>>>,
24 Json(payload): Json<DownloadOptions>,
25) -> StatusCode {
26 info!(payload = ?payload, "收到下载 HTTP 请求");
27 match tx.send(payload) {
28 Ok(()) => StatusCode::CREATED,
29 Err(e) => {
30 error!(err = ?e, "无法发送下载请求到主线程");
31 StatusCode::INTERNAL_SERVER_ERROR
32 }
33 }
34}
35
36pub async fn start_server(
37 app_core: App,
38 list_model: Rc<VecModel<EntryData>>,
39 ui: Weak<MainWindow>,
40) -> color_eyre::Result<()> {
41 let (tx, rx) = crossfire::mpsc::unbounded_async::<DownloadOptions>();
42 slint::spawn_local(async move {
43 while let Ok(e) = rx.recv().await {
44 let mut config = app_core.db.get_ui_download_config();
45 if let Some(s) = e.headers {
46 config.headers = s.into();
47 }
48 start_new_entry(&app_core, e.url, &config, &list_model);
49 let _ = ui.upgrade_in_event_loop(|ui| {
50 wakeup_window(&ui);
51 });
52 }
53 })
54 .log_err("启动后台 HTTP 请求处理服务失败")?;
55 tokio::spawn(async move {
56 let app = Router::new()
57 .route("/download", post(download))
58 .with_state(tx);
59 let listener = loop {
60 let res = tokio::net::TcpListener::bind("0.0.0.0:6121").await;
61 match res {
62 Ok(listener) => break listener,
63 Err(e) => error!(err = ?e, "Failed to bind to port 6121"),
64 }
65 tokio::time::sleep(Duration::from_secs(1)).await;
66 };
67 axum::serve(listener, app)
68 .await
69 .log_err("无法启动 server")
70 .unwrap();
71 info!("成功启动 server");
72 });
73 Ok(())
74}