fix(linux): stream original CUPS jobs directly to desktop capture
Some checks failed
Desktop Release / Bootstrap updater signing key (push) Has been cancelled
Desktop Release / Windows x64 NSIS (push) Has been cancelled
Desktop Release / Linux x64 DEB + AppImage (push) Has been cancelled
Desktop Release / Publish public desktop release (push) Has been cancelled
Some checks failed
Desktop Release / Bootstrap updater signing key (push) Has been cancelled
Desktop Release / Windows x64 NSIS (push) Has been cancelled
Desktop Release / Linux x64 DEB + AppImage (push) Has been cancelled
Desktop Release / Publish public desktop release (push) Has been cancelled
This commit is contained in:
parent
5252bca417
commit
c192e74229
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@fieldlens/print",
|
||||
"version": "0.1.9",
|
||||
"version": "0.1.10",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "fieldlens-print"
|
||||
version = "0.1.9"
|
||||
version = "0.1.10"
|
||||
description = "FieldLens print-stream barcode capture companion"
|
||||
authors = ["Arrowstack"]
|
||||
edition = "2021"
|
||||
|
||||
@ -25,9 +25,13 @@ use crate::{
|
||||
};
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
const SYSTEM_CUPS_SPOOL_DIR: &str = "/var/spool/fieldlens-print";
|
||||
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<AtomicBool>,
|
||||
@ -104,6 +108,28 @@ pub fn start(config: AppConfig, state: SharedState, app: AppHandle) -> Result<Ca
|
||||
}));
|
||||
}
|
||||
|
||||
for (address, source, forwarded) in [
|
||||
(CUPS_ORIGINAL_CAPTURE_ADDR, "cups-original", true),
|
||||
(CUPS_TEST_CAPTURE_ADDR, "cups-test", false),
|
||||
] {
|
||||
if let Ok(listener) = TcpListener::bind(address) {
|
||||
let _ = listener.set_nonblocking(true);
|
||||
let worker_stop = stop.clone();
|
||||
let worker_state = state.clone();
|
||||
let worker_app = app.clone();
|
||||
handles.push(thread::spawn(move || {
|
||||
cups_document_capture_loop(
|
||||
listener,
|
||||
worker_state,
|
||||
worker_app,
|
||||
worker_stop,
|
||||
source,
|
||||
forwarded,
|
||||
)
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
let sink = TcpListener::bind(TEST_CAPTURE_SINK_ADDR)
|
||||
.with_context(|| format!("bind test-capture sink {TEST_CAPTURE_SINK_ADDR}"))?;
|
||||
sink.set_nonblocking(true)?;
|
||||
@ -118,6 +144,91 @@ pub fn start(config: AppConfig, state: SharedState, app: AppHandle) -> Result<Ca
|
||||
Ok(CaptureRuntime { stop, handles })
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn cups_document_capture_loop(
|
||||
listener: TcpListener,
|
||||
state: SharedState,
|
||||
app: AppHandle,
|
||||
stop: Arc<AtomicBool>,
|
||||
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<AtomicBool>) {
|
||||
while !stop.load(Ordering::SeqCst) {
|
||||
|
||||
@ -20,15 +20,16 @@ const TEST_CAPTURE_DEVICE_URI: &str = "socket://127.0.0.1:9101";
|
||||
const WINDOWS_PORT_NAME: &str = "FieldLensPrint-9100";
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
const FIELDLENS_CUPS_SPOOL_DIR: &str = "/var/spool/fieldlens-print";
|
||||
const FIELDLENS_CUPS_SPOOL_DIR: &str = "/var/cache/cups/fieldlens-print";
|
||||
#[cfg(target_os = "linux")]
|
||||
const FIELDLENS_CUPS_FILTER: &str = r#"#!/usr/bin/python3
|
||||
import os
|
||||
import re
|
||||
import socket
|
||||
import sys
|
||||
import uuid
|
||||
|
||||
SPOOL = "/var/spool/fieldlens-print"
|
||||
SPOOL = os.path.join(os.environ.get("CUPS_CACHEDIR", "/var/cache/cups"), "fieldlens-print")
|
||||
|
||||
def safe(value):
|
||||
value = re.sub(r"[^A-Za-z0-9._-]+", "-", value or "job").strip("-")
|
||||
@ -50,14 +51,25 @@ 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
|
||||
@ -66,6 +78,17 @@ try:
|
||||
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)
|
||||
@ -75,22 +98,44 @@ try:
|
||||
except OSError:
|
||||
pass
|
||||
capture = None
|
||||
|
||||
sys.stdout.buffer.write(chunk)
|
||||
|
||||
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
|
||||
os.chmod(part_path, 0o644)
|
||||
os.replace(part_path, final_path)
|
||||
captured_ok = True
|
||||
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
|
||||
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 captured_ok:
|
||||
if not live_ok and not captured_ok:
|
||||
try:
|
||||
os.unlink(part_path)
|
||||
except OSError:
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://schema.tauri.app/config/2",
|
||||
"productName": "FieldLens Print",
|
||||
"version": "0.1.9",
|
||||
"version": "0.1.10",
|
||||
"identifier": "dev.arrowstack.fieldlens.print",
|
||||
"build": {
|
||||
"beforeDevCommand": "npm run dev",
|
||||
|
||||
@ -85,7 +85,7 @@ let config: AppConfig
|
||||
let jobs: Job[] = []
|
||||
let status: Status
|
||||
let loginTimer: number | undefined
|
||||
let currentVersion = '0.1.9'
|
||||
let currentVersion = '0.1.10'
|
||||
let bundleType: BundleType | null = null
|
||||
let availableUpdate: Update | null = null
|
||||
let availableManualUpdate: ManualUpdate | null = null
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user