-
Notifications
You must be signed in to change notification settings - Fork 6
/
web.rs
248 lines (227 loc) · 7.8 KB
/
web.rs
1
2
3
4
5
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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
use crate::common::check;
use actix_files as fs;
use actix_files::NamedFile;
use actix_web::{get, post, web, App, HttpResponse, HttpServer, Responder, Result};
use serde::{Deserialize, Serialize};
use std::time;
use crate::common::task::{TaskManager, add_task, delete_task, list_task, update_task,
run_task, get_download_body,
system_tasks_export, system_tasks_import};
use std::sync::{Arc, Mutex};
use std::collections::HashMap;
use clokwerk::{Scheduler, TimeUnits};
use std::thread;
use std::time::Duration;
use actix_web::web::Redirect;
use futures::{StreamExt, TryStreamExt};
use std::io::Write;
use std::fs::File;
use actix_multipart::{
form::{
tempfile::{TempFile, TempFileConfig},
MultipartForm,
},
Multipart,
};
#[derive(Debug, Deserialize, Serialize)]
struct TaskDel {
task_id: String, //任务id
}
#[derive(Debug, Deserialize, Serialize)]
struct TaskDelResp {
result: bool, //是否成功
}
pub async fn check_ipv6() -> bool {
let result =
reqwest::get("http://[2606:2800:220:1:248:1893:25c8:1946]").await;
match result {
Ok(_) => {
true
}
Err(e) => {
// 处理错误,根据错误类型返回更探针对性的信息也可以
// HttpResponse::Ok().body(format!("IPv6 might not be supported: {}", e))
false
}
}
}
#[derive(Serialize, Deserialize)]
struct CheckUrlIsAvailableRequest {
url: String,
timeout: Option<i32>,
}
#[get("/check/url-is-available")]
async fn check_url_is_available(req: web::Query<CheckUrlIsAvailableRequest>) -> impl Responder {
let mut timeout = 0;
if let Some(i) = req.timeout {
timeout = i;
}
let res = check::check::check_link_is_valid(req.url.to_owned(),
timeout as u64, true, true);
match res.await {
Ok(data) => {
let obj = serde_json::to_string(&data).unwrap();
return HttpResponse::Ok().body(obj);
}
Err(e) => {
println!("{}", e);
return HttpResponse::InternalServerError().body("{\"msg\":\"internal error\"}");
}
};
}
#[derive(Serialize, Deserialize)]
struct FetchM3uBodyRequest {
url: String,
timeout: Option<i32>,
}
#[get("/fetch/m3u-body")]
async fn fetch_m3u_body(req: web::Query<FetchM3uBodyRequest>) -> impl Responder {
let mut timeout = 0;
if let Some(i) = req.timeout {
timeout = i;
}
let client = reqwest::Client::builder()
.timeout(time::Duration::from_millis(timeout as u64))
.danger_accept_invalid_certs(true)
.build()
.unwrap();
let resp = client.get(req.url.to_owned()).send().await;
match resp {
Ok(res) => {
if res.status().is_success() {
let body = res.text().await;
match body {
Ok(text) => {
return HttpResponse::Ok().body(text);
}
Err(e) => {
println!("resp status error : {}", e);
return HttpResponse::InternalServerError()
.body("{\"msg\":\"internal error, fetch body error\"}");
}
}
}
return HttpResponse::InternalServerError()
.body("{\"msg\":\"internal error, status is not 200\"}");
}
Err(e) => {
println!("fetch error : {}", e);
return HttpResponse::InternalServerError()
.body("{\"msg\":\"internal error, fetch error\"}");
}
};
}
pub static VIEW_BASE_DIR: &str = "./static/";
#[derive(Serialize, Deserialize)]
struct SystemStatusResp {
can_ipv6: bool,
version: String,
output: String,
}
#[get("/system/info")]
async fn system_status() -> impl Responder {
let check_ipv6 = check_ipv6().await;
let system_status = SystemStatusResp {
can_ipv6: check_ipv6,
version: env!("CARGO_PKG_VERSION").to_string(),
output: format!("{}{}", VIEW_BASE_DIR, "upload".to_string()),
};
let obj = serde_json::to_string(&system_status).unwrap();
return HttpResponse::Ok().append_header(("Content-Type", "application/json")).body(obj);
}
#[get("/")]
async fn index() -> impl Responder {
let path: std::path::PathBuf = "./web/index.html".into(); // 替换为实际的 index.html 路径
NamedFile::open(path)
}
#[derive(Debug, MultipartForm)]
struct UploadFormReq {
#[multipart(rename = "file")]
file: TempFile,
}
#[derive(Serialize, Deserialize)]
struct UploadResponse {
msg: String,
url: String,
}
#[post("/media/upload")]
async fn upload(
MultipartForm(form): MultipartForm<UploadFormReq>,
) -> impl Responder {
let path = format!("static/input/{}", form.file.file_name.unwrap());
log::info!("saving to {path}");
form.file.file.persist(path.clone()).unwrap();
let resp = UploadResponse {
msg: "success".to_string(),
url: path.clone().to_string(),
};
let obj = serde_json::to_string(&resp).unwrap();
return HttpResponse::Ok().append_header(("Content-Type", "application/json")).body(obj);
}
pub async fn start_web(port: u16) {
let data = Arc::new(TaskManager {
tasks: Mutex::new(HashMap::new()),
});
// 尝试从文件加载任务
if let Err(e) = data.load_tasks() {
eprintln!("Failed to load tasks: {}", e);
}
// 使用 Arc<Mutex<Scheduler>> 来共享 scheduler
let scheduler: Arc<Mutex<Scheduler>> = Arc::new(Mutex::new(Scheduler::with_tz(chrono::Local)));
// 创建一个新线程来运行定时任务
let scheduler_thread = {
let scheduler = Arc::clone(&scheduler);
thread::spawn(move || {
loop {
{
let mut scheduler = scheduler.lock().unwrap();
scheduler.run_pending();
}
thread::sleep(Duration::from_secs(30));
}
})
};
let data_clone = Arc::clone(&data);
{
let mut scheduler = scheduler.lock().unwrap();
scheduler.every(30.seconds()).run(move || {
let data_clone = Arc::clone(&data_clone);
let tasks = data_clone.list_task().unwrap();
for mut task in tasks {
task.run();
data_clone.update_task_info(task.get_uuid(), task.get_task_info()).unwrap();
}
});
}
let data_clone_for_http = Arc::clone(&data);
let _ = HttpServer::new(move || {
let data_clone_for_http_server = Arc::clone(&data_clone_for_http);
App::new()
.service(check_url_is_available)
.service(fetch_m3u_body)
.service(system_status)
.service(index)
.service(upload)
.service(
fs::Files::new("/static", VIEW_BASE_DIR.to_owned())
.show_files_listing(),
)
.app_data(web::Data::new(data_clone_for_http_server))
.app_data(web::Data::new(scheduler.clone()))
.route("/tasks/list", web::get().to(list_task))
.route("/tasks/run", web::get().to(run_task))
.route("/tasks/update", web::post().to(update_task))
.route("/tasks/add", web::post().to(add_task))
.route("/tasks/get-download-body", web::get().to(get_download_body))
.route("/system/tasks/export", web::get().to(system_tasks_export))
.route("/system/tasks/import", web::post().to(system_tasks_import))
.route("/tasks/delete/{id}", web::delete().to(delete_task))
.service(fs::Files::new("/assets", "./web/assets"))
})
.bind(("0.0.0.0", port))
.expect("Failed to bind address")
.run()
.await
.expect("failed to run server");
scheduler_thread.join().unwrap();
}