From a65b79e886a03fe1223702c34d73573d2ac027bf Mon Sep 17 00:00:00 2001 From: Admin Date: Thu, 24 Sep 2026 05:19:29 +0000 Subject: [PATCH] fix(linux): use CUPS cache for pre-driver capture handoff --- package.json | 2 +- src-tauri/Cargo.toml | 2 +- src-tauri/src/capture.rs | 146 ++++++++------------------------------ src-tauri/src/printers.rs | 85 +++++++--------------- src-tauri/tauri.conf.json | 2 +- src/main.ts | 2 +- 6 files changed, 59 insertions(+), 180 deletions(-) diff --git a/package.json b/package.json index c6698d3..096621b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@fieldlens/print", - "version": "0.1.10", + "version": "0.1.11", "private": true, "type": "module", "scripts": { diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 7ae2a61..fe2e7df 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "fieldlens-print" -version = "0.1.10" +version = "0.1.11" description = "FieldLens print-stream barcode capture companion" authors = ["Arrowstack"] edition = "2021" diff --git a/src-tauri/src/capture.rs b/src-tauri/src/capture.rs index 7bb4242..3bc5425 100644 --- a/src-tauri/src/capture.rs +++ b/src-tauri/src/capture.rs @@ -28,10 +28,6 @@ use crate::{ const SYSTEM_CUPS_SPOOL_DIR: &str = "/var/cache/cups/fieldlens-print"; #[cfg(target_os = "linux")] const TEST_CAPTURE_SINK_ADDR: &str = "127.0.0.1:9101"; -#[cfg(target_os = "linux")] -const CUPS_ORIGINAL_CAPTURE_ADDR: &str = "127.0.0.1:9102"; -#[cfg(target_os = "linux")] -const CUPS_TEST_CAPTURE_ADDR: &str = "127.0.0.1:9103"; pub struct CaptureRuntime { stop: Arc, @@ -90,46 +86,22 @@ pub fn start(config: AppConfig, state: SharedState, app: AppHandle) -> Result Result, - source: &'static str, - forwarded: bool, -) { - while !stop.load(Ordering::SeqCst) { - match listener.accept() { - Ok((stream, _)) => { - let worker_state = state.clone(); - let worker_app = app.clone(); - thread::spawn(move || { - handle_cups_document_connection( - stream, - worker_state, - worker_app, - source, - forwarded, - ) - }); - } - Err(error) if error.kind() == ErrorKind::WouldBlock => { - thread::sleep(Duration::from_millis(80)); - } - Err(_) => { - thread::sleep(Duration::from_millis(250)); - } - } - } -} - -#[cfg(target_os = "linux")] -fn handle_cups_document_connection( - mut stream: TcpStream, - state: SharedState, - app: AppHandle, - source: &'static str, - forwarded: bool, -) { - let _ = stream.set_read_timeout(Some(Duration::from_secs(4))); - let mut bytes = Vec::new(); - let mut buffer = [0u8; 32 * 1024]; - - loop { - match stream.read(&mut buffer) { - Ok(0) => break, - Ok(read) => bytes.extend_from_slice(&buffer[..read]), - Err(error) if matches!(error.kind(), ErrorKind::WouldBlock | ErrorKind::TimedOut) => { - if !bytes.is_empty() { - break; - } - } - Err(error) => { - process_payload( - &state, - &app, - source, - "cups-live-capture".into(), - bytes, - forwarded, - Some(format!("CUPS live capture read failed: {error}")), - ); - return; - } - } - } - - if bytes.is_empty() { - return; - } - - process_payload( - &state, - &app, - source, - format!("cups-live-{}.pdf", Uuid::new_v4()), - bytes, - forwarded, - None, - ); -} - #[cfg(target_os = "linux")] fn test_capture_sink_loop(listener: TcpListener, stop: Arc) { while !stop.load(Ordering::SeqCst) { @@ -378,6 +265,33 @@ fn handle_raw_connection( ); } +#[cfg(target_os = "linux")] +fn cups_cache_folder_loop( + spool_dir: PathBuf, + config: AppConfig, + state: SharedState, + app: AppHandle, + stop: Arc, +) { + while !stop.load(Ordering::SeqCst) && !spool_dir.is_dir() { + thread::sleep(Duration::from_millis(500)); + } + + if stop.load(Ordering::SeqCst) { + return; + } + + spool_folder_loop( + config, + state, + app, + stop, + Some(spool_dir), + "cups-original", + true, + ); +} + fn spool_folder_loop( config: AppConfig, state: SharedState, diff --git a/src-tauri/src/printers.rs b/src-tauri/src/printers.rs index 9668e11..7581252 100644 --- a/src-tauri/src/printers.rs +++ b/src-tauri/src/printers.rs @@ -25,9 +25,7 @@ const FIELDLENS_CUPS_SPOOL_DIR: &str = "/var/cache/cups/fieldlens-print"; const FIELDLENS_CUPS_FILTER: &str = r#"#!/usr/bin/python3 import os import re -import socket import sys -import uuid SPOOL = os.path.join(os.environ.get("CUPS_CACHEDIR", "/var/cache/cups"), "fieldlens-print") @@ -39,60 +37,45 @@ job_id = safe(sys.argv[1] if len(sys.argv) > 1 else "job") title = safe(sys.argv[3] if len(sys.argv) > 3 else "print") printer = safe(os.environ.get("PRINTER", "printer")) content_type = os.environ.get("CONTENT_TYPE", "application/pdf") -ext = { - "application/pdf": "pdf", - "application/postscript": "ps", - "image/png": "png", - "image/jpeg": "jpg", -}.get(content_type, "dat") -prefix = "test-" if printer.lower() == "fieldlens-test-capture" else "print-" -name = f"{prefix}{printer}-{job_id}-{title}-{uuid.uuid4().hex}.{ext}" +is_test = printer.lower() == "fieldlens-test-capture" +prefix = "test-" if is_test else "print-" +ext = "pdf" if "pdf" in content_type else "dat" + +# Use a deterministic name so multiple PDF pre-filter stages for the same +# CUPS job do not create duplicate FieldLens jobs. +name = f"{prefix}{printer}-{job_id}-{title}.{ext}" final_path = os.path.join(SPOOL, name) part_path = final_path + ".part" source = None -live = None capture = None captured_ok = False -live_ok = False try: if len(sys.argv) > 6 and sys.argv[6]: source = open(sys.argv[6], "rb") else: source = sys.stdin.buffer - live_port = 9103 if printer.lower() == "fieldlens-test-capture" else 9102 - try: - live = socket.create_connection(("127.0.0.1", live_port), timeout=0.35) - live.settimeout(None) - except OSError: - live = None - - try: - os.makedirs(SPOOL, exist_ok=True) - capture = open(part_path, "wb") - except OSError: - capture = None + # If an earlier pre-filter stage already captured this job, only pass the + # bytes onward. Otherwise tee the pre-driver document into CUPS_CACHEDIR. + if not os.path.exists(final_path): + try: + os.makedirs(SPOOL, exist_ok=True) + capture = open(part_path, "wb") + except OSError as error: + print(f"WARNING: FieldLens capture cache unavailable: {error}", file=sys.stderr) + capture = None while True: chunk = source.read(65536) if not chunk: break - if live is not None: - try: - live.sendall(chunk) - except OSError: - try: - live.close() - except OSError: - pass - live = None - if capture is not None: try: capture.write(chunk) - except OSError: + except OSError as error: + print(f"WARNING: FieldLens capture write failed: {error}", file=sys.stderr) try: capture.close() except OSError: @@ -103,39 +86,20 @@ try: sys.stdout.buffer.flush() - if live is not None: - try: - live.shutdown(socket.SHUT_WR) - except OSError: - pass - live.close() - live = None - live_ok = True - if capture is not None: capture.flush() capture.close() capture = None - if live_ok: - try: - os.unlink(part_path) - except OSError: - pass - else: - os.chmod(part_path, 0o644) - os.replace(part_path, final_path) - captured_ok = True + os.chmod(part_path, 0o644) + os.replace(part_path, final_path) + captured_ok = True + print(f"INFO: FieldLens captured {name}", file=sys.stderr) finally: if source is not None and source is not sys.stdin.buffer: source.close() - if live is not None: - try: - live.close() - except OSError: - pass if capture is not None: capture.close() - if not live_ok and not captured_ok: + if not captured_ok: try: os.unlink(part_path) except OSError: @@ -620,7 +584,8 @@ fn build_fieldlens_ppd(target_printer: &str) -> Result { let prefilter = concat!( "*% FieldLens original-document capture before printer rendering\n", - "*cupsPreFilter: \"application/pdf 0 fieldlens-capture\"\n" + "*cupsPreFilter: \"application/pdf 0 fieldlens-capture\"\n", + "*cupsPreFilter: \"application/vnd.cups-pdf 0 fieldlens-capture\"\n" ); if let Some(index) = cleaned.rfind("*% End") { cleaned.insert_str(index, prefilter); diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 3223f86..c152fe7 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "FieldLens Print", - "version": "0.1.10", + "version": "0.1.11", "identifier": "dev.arrowstack.fieldlens.print", "build": { "beforeDevCommand": "npm run dev", diff --git a/src/main.ts b/src/main.ts index 346c993..f42ffea 100644 --- a/src/main.ts +++ b/src/main.ts @@ -85,7 +85,7 @@ let config: AppConfig let jobs: Job[] = [] let status: Status let loginTimer: number | undefined -let currentVersion = '0.1.10' +let currentVersion = '0.1.11' let bundleType: BundleType | null = null let availableUpdate: Update | null = null let availableManualUpdate: ManualUpdate | null = null