Dualside print update realisation
This commit is contained in:
94
src/main.rs
94
src/main.rs
@ -25,7 +25,7 @@ use tracing::{error, info, warn};
|
||||
|
||||
async fn get_version() -> impl IntoResponse {
|
||||
Json(serde_json::json!({
|
||||
"version": "1.0.3",
|
||||
"version": "1.0.4",
|
||||
"timestamp": chrono::Utc::now().timestamp()
|
||||
}))
|
||||
}
|
||||
@ -376,7 +376,7 @@ async fn job_cancel(
|
||||
for id in cups_ids {
|
||||
let cancel_bin = cups_bin("cancel");
|
||||
let _ = tokio::task::spawn_blocking(move || {
|
||||
Command::new(cancel_bin).arg(id).output()
|
||||
Command::new(cancel_bin).arg(id).env("LC_ALL", "C").output()
|
||||
})
|
||||
.await;
|
||||
}
|
||||
@ -571,6 +571,22 @@ fn run_lp(path: &Path, settings: Option<&PrintSettings>, duplex_pass: Option<&st
|
||||
}
|
||||
}
|
||||
|
||||
// Выбор лотка (InputSlot)
|
||||
if let Some(pass) = duplex_pass {
|
||||
if pass == "even" {
|
||||
// Второй проход (чётные страницы) — ручная подача
|
||||
cmd.arg("-o").arg("InputSlot=Manual");
|
||||
// Поворот на 180° для второго прохода
|
||||
cmd.arg("-o").arg("orientation-requested=6");
|
||||
} else {
|
||||
// Первый проход (нечётные страницы) — нижний лоток
|
||||
cmd.arg("-o").arg("InputSlot=Lower");
|
||||
}
|
||||
} else {
|
||||
// Обычная печать — нижний лоток
|
||||
cmd.arg("-o").arg("InputSlot=Lower");
|
||||
}
|
||||
|
||||
// Ручной дуплекс: разбивка на чёт/нечёт
|
||||
match duplex_pass {
|
||||
Some("odd") => {
|
||||
@ -588,7 +604,8 @@ fn run_lp(path: &Path, settings: Option<&PrintSettings>, duplex_pass: Option<&st
|
||||
let args: Vec<String> = cmd.get_args().map(|a| a.to_string_lossy().to_string()).collect();
|
||||
info!(command = format!("{} {}", lp.display(), args.join(" ")), "🖨️ Команда печати");
|
||||
|
||||
let out = cmd.output().map_err(|e| format!("Не удалось запустить lp: {}", e))?;
|
||||
// ВАЖНО: LC_ALL=C гарантирует, что ответ будет на английском и парсинг ID не сломается
|
||||
let out = cmd.env("LC_ALL", "C").output().map_err(|e| format!("Не удалось запустить lp: {}", e))?;
|
||||
if !out.status.success() {
|
||||
return Err(String::from_utf8_lossy(&out.stderr).trim().to_string());
|
||||
}
|
||||
@ -596,18 +613,6 @@ fn run_lp(path: &Path, settings: Option<&PrintSettings>, duplex_pass: Option<&st
|
||||
Ok(stdout.split_whitespace().nth(3).unwrap_or("").to_string())
|
||||
}
|
||||
|
||||
fn lpstat_raw(which: &str) -> Option<String> {
|
||||
let lpstat = cups_bin("lpstat");
|
||||
Command::new(&lpstat)
|
||||
.arg("-W")
|
||||
.arg(which)
|
||||
.arg("-o")
|
||||
.output()
|
||||
.ok()
|
||||
.filter(|o| o.status.success())
|
||||
.map(|o| String::from_utf8_lossy(&o.stdout).to_string())
|
||||
}
|
||||
|
||||
fn has_job_in(output: &Option<String>, id: &str) -> bool {
|
||||
output
|
||||
.as_deref()
|
||||
@ -618,15 +623,25 @@ fn has_job_in(output: &Option<String>, id: &str) -> bool {
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Ждём, пока CUPS-job исчезнет из не-завершённых.
|
||||
/// «Не завершён» = всё, что в состоянии queued / active / held (принтер не ready).
|
||||
fn lpstat_raw(which: &str) -> Option<String> {
|
||||
let lpstat = cups_bin("lpstat");
|
||||
Command::new(&lpstat)
|
||||
.arg("-W")
|
||||
.arg(which)
|
||||
.arg("-o")
|
||||
.env("LC_ALL", "C")
|
||||
.output()
|
||||
.ok()
|
||||
.filter(|o| o.status.success())
|
||||
.map(|o| String::from_utf8_lossy(&o.stdout).to_string())
|
||||
}
|
||||
|
||||
fn wait_for_cups(id: &str) -> Result<(), String> {
|
||||
let lpstat_path = cups_bin("lpstat");
|
||||
// Если lpstat физически отсутствует — не можем отслеживать, возвращаем ошибку
|
||||
if !lpstat_path.exists() && lpstat_path.to_str().unwrap_or("") == "lpstat" {
|
||||
// пробуем найти через which
|
||||
if Command::new("which")
|
||||
.arg("lpstat")
|
||||
.env("LC_ALL", "C")
|
||||
.output()
|
||||
.map(|o| !o.status.success())
|
||||
.unwrap_or(true)
|
||||
@ -640,22 +655,26 @@ fn wait_for_cups(id: &str) -> Result<(), String> {
|
||||
let started = Instant::now();
|
||||
let timeout = Duration::from_secs(30 * 60); // 30 минут максимум
|
||||
|
||||
// Первый опрос: проверяем что lpstat вообще работает
|
||||
let probe = lpstat_raw("not-completed");
|
||||
if probe.is_none() {
|
||||
return Err("lpstat не отвечает (возможно, не запущен cupsd)".into());
|
||||
}
|
||||
|
||||
// Получаем имя принтера по умолчанию для проверки физического статуса
|
||||
let printer_name = get_default_printer().unwrap_or_else(|| "Auto_HP_P2015".to_string());
|
||||
|
||||
loop {
|
||||
// "not-completed" = все задания, которые ещё не finished/cancelled
|
||||
// Сюда попадают: pending, held, processing, а также "not ready" задания
|
||||
let not_completed = lpstat_raw("not-completed");
|
||||
let in_queue = has_job_in(¬_completed, id);
|
||||
|
||||
if !in_queue {
|
||||
// Либо допечаталось (в completed), либо отменили — в любом случае наша работа сделана
|
||||
info!(cups_id = %id, "✔ CUPS-задание покинуло очередь (допечатано/отменено)");
|
||||
// Задачи уже нет в очереди CUPS. Проверяем, допечатал ли принтер физически.
|
||||
if !is_printer_physically_printing(&printer_name) {
|
||||
info!(cups_id = %id, "✔ CUPS-задание выполнено и принтер физически допечатал");
|
||||
return Ok(());
|
||||
} else {
|
||||
info!(cups_id = %id, "⏳ CUPS передал данные, но принтер всё ещё физически печатает из буфера...");
|
||||
}
|
||||
}
|
||||
|
||||
if started.elapsed() > timeout {
|
||||
@ -665,3 +684,30 @@ fn wait_for_cups(id: &str) -> Result<(), String> {
|
||||
std::thread::sleep(Duration::from_millis(1500));
|
||||
}
|
||||
}
|
||||
|
||||
fn get_default_printer() -> Option<String> {
|
||||
let out = Command::new("lpstat")
|
||||
.arg("-d")
|
||||
.env("LC_ALL", "C")
|
||||
.output()
|
||||
.ok()?;
|
||||
let stdout = String::from_utf8_lossy(&out.stdout);
|
||||
// Формат: "system default destination: Auto_HP_P2015"
|
||||
stdout.split(':').nth(1).map(|s| s.trim().to_string())
|
||||
}
|
||||
|
||||
fn is_printer_physically_printing(printer_name: &str) -> bool {
|
||||
if let Ok(out) = Command::new("lpstat")
|
||||
.arg("-p")
|
||||
.arg(printer_name)
|
||||
.env("LC_ALL", "C")
|
||||
.output()
|
||||
{
|
||||
let stdout = String::from_utf8_lossy(&out.stdout);
|
||||
// "now printing" или "processing" означают, что принтер физически печатает из буфера
|
||||
if stdout.contains("now printing") || stdout.contains("processing") {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user