init
This commit is contained in:
143
src/main.rs
Normal file
143
src/main.rs
Normal file
@ -0,0 +1,143 @@
|
||||
use axum::{
|
||||
extract::Multipart,
|
||||
http::StatusCode,
|
||||
response::IntoResponse,
|
||||
routing::post,
|
||||
Router,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use std::process::Command;
|
||||
use tempfile::NamedTempFile;
|
||||
use tokio::fs;
|
||||
use tower_http::cors::CorsLayer;
|
||||
|
||||
#[derive(Deserialize, Clone)] // Добавили Clone
|
||||
struct PrintSettings {
|
||||
filename: String,
|
||||
pages: String,
|
||||
copies: u32,
|
||||
#[serde(rename = "colorMode")]
|
||||
color_mode: String,
|
||||
format: String,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
let cors = CorsLayer::permissive();
|
||||
|
||||
let app = Router::new()
|
||||
.route("/print", post(handle_print))
|
||||
.layer(cors);
|
||||
|
||||
println!("🖨️ Print API запущен на http://0.0.0.0:3000");
|
||||
|
||||
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000")
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
axum::serve(listener, app).await.unwrap();
|
||||
}
|
||||
|
||||
async fn handle_print(mut multipart: Multipart) -> impl IntoResponse {
|
||||
let mut printed_files: Vec<String> = Vec::new();
|
||||
let mut errors: Vec<String> = Vec::new();
|
||||
|
||||
let mut settings_map: std::collections::HashMap<String, PrintSettings> = std::collections::HashMap::new();
|
||||
let mut files_data: Vec<(String, bytes::Bytes)> = Vec::new();
|
||||
|
||||
while let Some(field) = multipart.next_field().await.unwrap_or(None) {
|
||||
let field_name = field.name().unwrap_or("").to_string();
|
||||
|
||||
if field_name == "settings" {
|
||||
if let Ok(text) = field.text().await {
|
||||
if let Ok(settings) = serde_json::from_str::<PrintSettings>(&text) {
|
||||
settings_map.insert(settings.filename.clone(), settings);
|
||||
}
|
||||
}
|
||||
} else if field_name == "files" {
|
||||
let file_name = field.file_name().unwrap_or("unknown").to_string();
|
||||
if let Ok(data) = field.bytes().await {
|
||||
files_data.push((file_name, data));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (file_name, data) in files_data {
|
||||
// Клонируем настройки перед использованием
|
||||
let settings = settings_map.get(&file_name).cloned();
|
||||
|
||||
let ext = std::path::Path::new(&file_name)
|
||||
.extension()
|
||||
.and_then(|e| e.to_str())
|
||||
.unwrap_or("");
|
||||
|
||||
let temp_file = if ext.is_empty() {
|
||||
NamedTempFile::new()
|
||||
} else {
|
||||
NamedTempFile::with_suffix(&format!(".{}", ext))
|
||||
};
|
||||
|
||||
let temp_file = match temp_file {
|
||||
Ok(f) => f,
|
||||
Err(e) => {
|
||||
errors.push(format!("Не удалось создать temp файл для {}: {}", file_name, e));
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let path = temp_file.path().to_path_buf();
|
||||
|
||||
if let Err(e) = fs::write(&path, &data).await {
|
||||
errors.push(format!("Ошибка записи {}: {}", file_name, e));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Теперь settings это Option<PrintSettings>, а не ссылка
|
||||
let print_result = tokio::task::spawn_blocking(move || {
|
||||
let mut cmd = Command::new("lp");
|
||||
|
||||
if let Some(s) = settings {
|
||||
cmd.arg("-n").arg(s.copies.to_string());
|
||||
|
||||
if s.pages != "all" && !s.pages.is_empty() {
|
||||
cmd.arg("-P").arg(&s.pages);
|
||||
}
|
||||
|
||||
if s.color_mode == "bw" {
|
||||
cmd.arg("-o").arg("ColorModel=Gray");
|
||||
}
|
||||
|
||||
cmd.arg("-o").arg(format!("PageSize={}", s.format));
|
||||
}
|
||||
|
||||
cmd.arg(&path).output()
|
||||
})
|
||||
.await;
|
||||
|
||||
match print_result {
|
||||
Ok(Ok(output)) if output.status.success() => {
|
||||
printed_files.push(file_name);
|
||||
}
|
||||
Ok(Ok(output)) => {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
errors.push(format!("lp ошибка для {}: {}", file_name, stderr));
|
||||
}
|
||||
_ => {
|
||||
errors.push(format!("Не удалось запустить lp для {}", file_name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if errors.is_empty() && !printed_files.is_empty() {
|
||||
(StatusCode::OK, format!("Напечатано {} файлов", printed_files.len()))
|
||||
} else if printed_files.is_empty() {
|
||||
(StatusCode::BAD_REQUEST, format!("Ошибки: {:?}", errors))
|
||||
} else {
|
||||
(
|
||||
StatusCode::MULTI_STATUS,
|
||||
format!("Частично: {:?}, Ошибки: {:?}", printed_files, errors),
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user