mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-10 13:01:29 +00:00
* Print: list printers without asking drivers; ask only about the one picked (#2192) On Windows, craft-print's printers() reads every driver's capabilities to report two-sided support. Windows takes about 48 s to give up on a printer it can't reach, such as a WSD printer on another network, so the Print dialog waited for each one before it listed anything (#2192). The dialog now lists printers from craft-print's printer_queues(), which asks no driver, and asks two_sided() about the printer the user picks, on a background thread (PdfCraftApp::two_sided_source, set by the desktop app like printer_source). Each answer is kept for the session, so a printer is asked about at most once, and one nobody picks is never waited on. The "doesn't take two-sided settings" note shows once the answer arrives. The automation printers tool still uses printers(), so it keeps reporting two_sided. Needs the craft-libs change that adds craft_print::two_sided. * Build: take craft-print from storytold/craft-libs#133 Pins craft-print to the head of storytold/craft-libs#133 (b6a3b87), which adds craft_print::two_sided as one commit on craft-libs main at 5ee809f. craft-print is otherwise unchanged from 7ee35a9, the rev main pins now; the other craft-libs crates stay where they are. Fetched through the storytold/craft-libs URL, so cargo-deny's allow-git is unchanged. To be replaced by the merge commit once #133 lands. * Print: run at most four two-sided reads at once A driver Windows can't reach holds its two-sided read for about 48 s, so picking printer after printer started a thread for each. At most four now run at once; past that, the printer picked is asked once a read finishes. Tests: hanging reads are capped and the picked printer is asked after one finishes; a list that already says whether a driver takes two-sided settings is used as it is, with no driver asked. * Repin craft-print to craft-libs main, where #133 merged (b6b8a55b) --------- Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
This commit is contained in:
1 parent
ca66667f33
commit
dff02827fd
11 files changed
+237
-12
No files matched your search
Generated
+1
-1
@@ -1243,7 +1243,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "craft-print"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/storytold/craft-libs?rev=7ee35a9fbd47af572d55fd63dbf0333d1f00da17#7ee35a9fbd47af572d55fd63dbf0333d1f00da17"
|
||||
source = "git+https://github.com/storytold/craft-libs?rev=b6b8a55b2c52bb83d0580216cbecb532af01970d#b6b8a55b2c52bb83d0580216cbecb532af01970d"
|
||||
dependencies = [
|
||||
"craft-winprint-gdi",
|
||||
"log",
|
||||
|
||||
+1
-1
@@ -24,7 +24,7 @@ craft-keymap = { git = "https://github.com/storytold/craft-libs", version = "0.1
|
||||
# the commit in `craft-i18n.rev`). Already in the tree through craft-ui.
|
||||
craft-i18n = { git = "https://github.com/storytold/craft-libs", version = "0.1.0", rev = "787a7a0f972fd1d7a7618fdbeca3a5267a0407ff" }
|
||||
# Shared system spooler; document composition remains in pdfcraft-print.
|
||||
craft-print = { git = "https://github.com/storytold/craft-libs", version = "0.1.0", rev = "7ee35a9fbd47af572d55fd63dbf0333d1f00da17" }
|
||||
craft-print = { git = "https://github.com/storytold/craft-libs", version = "0.1.0", rev = "b6b8a55b2c52bb83d0580216cbecb532af01970d" }
|
||||
# Whether a path stored in a document may be opened (GoToR link targets).
|
||||
craft-docpath = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "787a7a0f972fd1d7a7618fdbeca3a5267a0407ff" }
|
||||
# craft-update: app-specific pretend versions apply to startup and manual checks.
|
||||
|
||||
@@ -475,6 +475,7 @@ fn app_creator<'a>(
|
||||
}
|
||||
app.set_startup_update_check(true);
|
||||
app.printer_source = Some(pdfcraft_ui_egui::system_printers());
|
||||
app.two_sided_source = Some(pdfcraft_ui_egui::system_two_sided());
|
||||
app.os_key_store_ids = cfg!(any(target_os = "macos", target_os = "windows"));
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
|
||||
@@ -24,8 +24,9 @@ thread_local! {
|
||||
/// Tests: the folder the print DLLs are looked for in, instead of the system's, so the
|
||||
/// public entry points can be checked against a Windows that lacks them.
|
||||
pub(crate) static SYSTEM32_FOR_TEST: std::cell::RefCell<Option<std::path::PathBuf>> = const { std::cell::RefCell::new(None) };
|
||||
/// Tests: how many times [`printers`] asked the system's printer backend, so a test can tell
|
||||
/// the guard answered without relying on the machine having no printers.
|
||||
/// Tests: how many times [`printers`], [`printer_queues`] or [`two_sided`] asked the system's
|
||||
/// printer backend, so a test can tell the guard answered without relying on the machine
|
||||
/// having no printers.
|
||||
pub(crate) static PRINTER_BACKEND_CALLS: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
|
||||
}
|
||||
|
||||
@@ -59,6 +60,29 @@ pub fn printers() -> Vec<Printer> {
|
||||
craft_print::printers()
|
||||
}
|
||||
|
||||
/// The system's print queues without asking any driver, so a Print dialog's list never waits on
|
||||
/// a printer (#2192); none on a Windows installation without the print DLLs, as [`printers`].
|
||||
pub fn printer_queues() -> Vec<Printer> {
|
||||
if missing_print_dll().is_some() {
|
||||
return Vec::new();
|
||||
}
|
||||
#[cfg(all(windows, test))]
|
||||
PRINTER_BACKEND_CALLS.with(|c| c.set(c.get().saturating_add(1)));
|
||||
craft_print::printer_queues()
|
||||
}
|
||||
|
||||
/// Whether `printer`'s driver takes two-sided settings, read for that one printer; it can wait
|
||||
/// on the device, so call it off the UI thread. Unknown on a Windows installation without the
|
||||
/// print DLLs: reading it goes through them.
|
||||
pub fn two_sided(printer: &str) -> Option<bool> {
|
||||
if missing_print_dll().is_some() {
|
||||
return None;
|
||||
}
|
||||
#[cfg(all(windows, test))]
|
||||
PRINTER_BACKEND_CALLS.with(|c| c.set(c.get().saturating_add(1)));
|
||||
craft_print::two_sided(printer)
|
||||
}
|
||||
|
||||
/// The driver's job options for a printer (CUPS: its PPD, with the queue's current defaults);
|
||||
/// empty when it has none or on other platforms. Reads files and runs `lpoptions`, so call it
|
||||
/// when the user asks, not every frame.
|
||||
|
||||
@@ -1547,7 +1547,7 @@ fn missing_print_dlls_are_named() {
|
||||
#[cfg(windows)]
|
||||
#[test]
|
||||
fn printing_without_the_print_dlls_is_refused_before_the_spooler() {
|
||||
use crate::spool::{PRINT_DLLS, PRINTER_BACKEND_CALLS, SYSTEM32_FOR_TEST, printers, submit, submit_with_progress};
|
||||
use crate::spool::{PRINT_DLLS, PRINTER_BACKEND_CALLS, SYSTEM32_FOR_TEST, printer_queues, printers, submit, submit_with_progress, two_sided};
|
||||
let dir = std::env::temp_dir().join(format!("pdfcraft-print-no-dlls-{}", std::process::id()));
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
@@ -1564,6 +1564,8 @@ fn printing_without_the_print_dlls_is_refused_before_the_spooler() {
|
||||
// Not only an empty list, which a machine without printers gives anyway: the backend wasn't asked.
|
||||
PRINTER_BACKEND_CALLS.with(|c| c.set(0));
|
||||
assert!(printers().is_empty(), "no printers are listed");
|
||||
assert!(printer_queues().is_empty(), "no queues are listed");
|
||||
assert_eq!(two_sided("PdfCraft test: no such printer"), None, "two-sided support is unknown");
|
||||
assert_eq!(PRINTER_BACKEND_CALLS.with(|c| c.get()), 0, "the printer backend wasn't asked");
|
||||
SYSTEM32_FOR_TEST.with(|d| *d.borrow_mut() = None);
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
|
||||
@@ -133,6 +133,7 @@ impl PdfCraftApp {
|
||||
printer_source: None,
|
||||
options_source: None,
|
||||
spooler: None,
|
||||
two_sided_source: None,
|
||||
print_tasks: print_ui::PrintTasks::default(),
|
||||
palette_open: false,
|
||||
palette_query: String::new(),
|
||||
|
||||
@@ -60,7 +60,8 @@ mod hotbar;
|
||||
mod panels;
|
||||
pub mod panes;
|
||||
pub use print_ui::{
|
||||
Handling as PrintHandling, OptionsSource, PrintDraft, PrinterSetup, PrinterSource, Spooler, Which as PrintWhich, system_printers,
|
||||
Handling as PrintHandling, OptionsSource, PrintDraft, PrinterSetup, PrinterSource, Spooler, TwoSidedSource, Which as PrintWhich, system_printers,
|
||||
system_two_sided,
|
||||
};
|
||||
pub use redact_ui::{HiddenDraft, PagesDraft as RedactPagesDraft, RedactPrefs, SearchDraft as RedactSearchDraft, SearchMode as RedactSearchMode};
|
||||
pub mod i18n;
|
||||
@@ -422,6 +423,9 @@ pub struct PdfCraftApp {
|
||||
pub options_source: Option<OptionsSource>,
|
||||
/// What print jobs go to: the system spooler unless a test stands in ([`Spooler`]).
|
||||
pub spooler: Option<Spooler>,
|
||||
/// Reads the picked printer's two-sided support in the background (the desktop app sets it;
|
||||
/// see [`TwoSidedSource`]). Without one the dialog goes by the printer list alone.
|
||||
pub two_sided_source: Option<TwoSidedSource>,
|
||||
// A browser has no background print work: the type is empty there.
|
||||
#[cfg_attr(target_arch = "wasm32", allow(dead_code))]
|
||||
pub(crate) print_tasks: print_ui::PrintTasks,
|
||||
|
||||
@@ -28,10 +28,15 @@ pub(crate) const PREVIEW_BOX: (f32, f32) = (PREVIEW_W - 2.0 * PREVIEW_INSET, PRE
|
||||
/// Label printing reads the printed area of at most this many pages (each is a quick render on
|
||||
/// the UI thread); further pages print whole.
|
||||
pub(crate) const LABEL_CROP_PAGES: usize = 50;
|
||||
/// Two-sided reads running at once. A driver Windows can't reach holds its read for about 48 s,
|
||||
/// so picking printer after printer could otherwise start a thread for each; past this, the
|
||||
/// printer picked is asked once a read finishes.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
const TWO_SIDED_READS: usize = 4;
|
||||
|
||||
/// Lists the printers off the UI thread (the desktop app sets [`PdfCraftApp::printer_source`]):
|
||||
/// on Windows each driver's capabilities are read for its two-sided support, and CUPS' driverless discovery can wait on
|
||||
/// the network. Without a source the dialog reads them as it opens, so tests stay deterministic.
|
||||
/// CUPS' driverless discovery can wait on the network. Without a source the dialog reads them as
|
||||
/// it opens, so tests stay deterministic.
|
||||
pub type PrinterSource = std::sync::Arc<dyn Fn() -> Vec<spool::Printer> + Send + Sync>;
|
||||
|
||||
/// Where a printer's driver options come from: the spooler's (`spool::printer_options_in`, in the
|
||||
@@ -42,9 +47,21 @@ pub type OptionsSource = std::sync::Arc<dyn Fn(&str) -> Vec<spool::PrinterOption
|
||||
/// print-ready PDF and the job and answers as the spooler would. Called off the GUI thread.
|
||||
pub type Spooler = std::sync::Arc<dyn Fn(&[u8], &spool::Job) -> Result<String, String> + Send + Sync>;
|
||||
|
||||
/// The system spooler's printers, as the desktop app's [`PrinterSource`].
|
||||
/// The system spooler's printers, as the desktop app's [`PrinterSource`]: its queues only, with
|
||||
/// no driver asked anything, so the list never waits on a printer (#2192).
|
||||
pub fn system_printers() -> PrinterSource {
|
||||
std::sync::Arc::new(spool::printers)
|
||||
std::sync::Arc::new(spool::printer_queues)
|
||||
}
|
||||
|
||||
/// Reads whether one printer's driver takes two-sided settings, off the UI thread, for the
|
||||
/// printer picked (the desktop app sets [`PdfCraftApp::two_sided_source`]). The printer list
|
||||
/// leaves it unknown: reading a driver Windows can't reach takes it about 48 s, so the list
|
||||
/// doesn't wait for every printer's (#2192).
|
||||
pub type TwoSidedSource = std::sync::Arc<dyn Fn(&str) -> Option<bool> + Send + Sync>;
|
||||
|
||||
/// The system spooler's answer, as the desktop app's [`TwoSidedSource`].
|
||||
pub fn system_two_sided() -> TwoSidedSource {
|
||||
std::sync::Arc::new(spool::two_sided)
|
||||
}
|
||||
|
||||
/// The Print dialog's work in the background: the printer list being read, and the jobs being
|
||||
@@ -53,6 +70,9 @@ pub fn system_printers() -> PrinterSource {
|
||||
pub(crate) struct PrintTasks {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
printers: Option<std::sync::mpsc::Receiver<Vec<spool::Printer>>>,
|
||||
/// Two-sided support being read, by printer.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
two_sided: Vec<(String, std::sync::mpsc::Receiver<Option<bool>>)>,
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
jobs: Vec<PrintJob>,
|
||||
/// The progress card is showing a print job (it is shared with other long jobs).
|
||||
@@ -82,7 +102,7 @@ fn listed_printers() -> Vec<spool::Printer> {
|
||||
}
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
spool::printers()
|
||||
spool::printer_queues()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -363,6 +383,7 @@ impl PdfCraftApp {
|
||||
d.printers_loading = false;
|
||||
}
|
||||
}
|
||||
self.poll_two_sided();
|
||||
let mut finished = Vec::new();
|
||||
self.print_tasks.jobs.retain_mut(|job| {
|
||||
loop {
|
||||
@@ -421,6 +442,48 @@ impl PdfCraftApp {
|
||||
}
|
||||
}
|
||||
|
||||
/// Pick up two-sided answers that arrived, and ask for the picked printer's when the list
|
||||
/// doesn't say, nobody has asked yet and fewer than [`TWO_SIDED_READS`] reads are running.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn poll_two_sided(&mut self) {
|
||||
let d = &mut self.print_draft;
|
||||
self.print_tasks.two_sided.retain(|(printer, rx)| match rx.try_recv() {
|
||||
Ok(two_sided) => {
|
||||
d.two_sided.insert(printer.clone(), two_sided);
|
||||
false
|
||||
}
|
||||
Err(std::sync::mpsc::TryRecvError::Empty) => true,
|
||||
Err(std::sync::mpsc::TryRecvError::Disconnected) => {
|
||||
d.two_sided.insert(printer.clone(), None);
|
||||
false
|
||||
}
|
||||
});
|
||||
if self.dialog != Some(crate::Dialog::Print) {
|
||||
return;
|
||||
}
|
||||
let Some(source) = self.two_sided_source.clone() else { return };
|
||||
let Some(printer) = d.printers.iter().find(|p| d.printer.as_ref() == Some(&p.name)) else { return };
|
||||
if printer.two_sided.is_some()
|
||||
|| d.two_sided.contains_key(&printer.name)
|
||||
|| self.print_tasks.two_sided.iter().any(|(n, _)| *n == printer.name)
|
||||
|| self.print_tasks.two_sided.len() >= TWO_SIDED_READS
|
||||
{
|
||||
return;
|
||||
}
|
||||
let name = printer.name.clone();
|
||||
let (tx, rx) = std::sync::mpsc::channel();
|
||||
let ctx = self.ctx.clone();
|
||||
let asked = name.clone();
|
||||
std::thread::spawn(move || {
|
||||
// The receiver may be gone (the app quit): nothing to report to then.
|
||||
let _ = tx.send(source(&asked));
|
||||
if let Some(ctx) = ctx {
|
||||
ctx.request_repaint();
|
||||
}
|
||||
});
|
||||
self.print_tasks.two_sided.push((name, rx));
|
||||
}
|
||||
|
||||
fn report_print_job(&mut self, printer: &str, result: Result<String, String>) {
|
||||
match result {
|
||||
Ok(msg) if msg.is_empty() => self.notify(crate::i18n::fmt(tl!("Sent to {printer}"), &[("printer", printer)])),
|
||||
|
||||
@@ -238,7 +238,7 @@ pub(crate) fn body(
|
||||
// Windows says which drivers can't print two-sided as they're asked; they keep their
|
||||
// own setting, even when Two-sided is Off.
|
||||
if let Some(p) = d.printers.iter().find(|p| d.printer.as_ref() == Some(&p.name))
|
||||
&& p.two_sided == Some(false)
|
||||
&& p.two_sided.or_else(|| d.two_sided.get(&p.name).copied().flatten()) == Some(false)
|
||||
{
|
||||
ui.label(egui::RichText::new(tl!("This printer's driver doesn't take two-sided settings from other programs, so its own setting applies. Change it in Properties…")).small().color(t.text_muted));
|
||||
}
|
||||
|
||||
@@ -94,6 +94,9 @@ pub struct PrintDraft {
|
||||
pub driver_options_loading: Option<String>,
|
||||
/// Properties… asked for this printer's options; the app starts the read on the next frame.
|
||||
pub read_options_for: Option<String>,
|
||||
/// Whether each printer's driver takes two-sided settings, as read in the background for
|
||||
/// the printer picked (see [`TwoSidedSource`](super::TwoSidedSource)); `None` when it couldn't be read.
|
||||
pub two_sided: std::collections::BTreeMap<String, Option<bool>>,
|
||||
/// The paper, sizing and two-sided choice each printer was last printed with, the printer
|
||||
/// printed to last first; kept across restarts (#2001).
|
||||
pub setups: Vec<(String, PrinterSetup)>,
|
||||
@@ -148,6 +151,7 @@ impl Default for PrintDraft {
|
||||
crop_pages: None,
|
||||
driver_options_loading: None,
|
||||
read_options_for: None,
|
||||
two_sided: Default::default(),
|
||||
setups: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -519,6 +519,46 @@ fn custom_page_cannot_silently_match_a_standard_paper() {
|
||||
assert_eq!(h.state().print_draft.size, before.size);
|
||||
}
|
||||
|
||||
/// Windows' printer list doesn't say which drivers take two-sided settings: reading a printer
|
||||
/// Windows can't reach takes it about 48 s (#2192). The dialog lists every printer at once and
|
||||
/// asks only about the one picked, in the background, so a printer nobody picks is never waited on.
|
||||
#[test]
|
||||
fn two_sided_support_is_read_only_for_the_printer_picked() {
|
||||
use pdfcraft_engine::print::spool::Printer;
|
||||
use std::sync::{Arc, Mutex};
|
||||
let mut h = harness();
|
||||
h.state_mut().printer_source = Some(Arc::new(|| {
|
||||
vec![
|
||||
Printer { name: "Office PCL".into(), default: true, two_sided: None, description: String::new() },
|
||||
Printer { name: "Office PS".into(), default: false, two_sided: None, description: String::new() },
|
||||
Printer { name: "Away WSD".into(), default: false, two_sided: None, description: String::new() },
|
||||
]
|
||||
}));
|
||||
let asked = Arc::new(Mutex::new(Vec::<String>::new()));
|
||||
let log = asked.clone();
|
||||
h.state_mut().two_sided_source = Some(Arc::new(move |name: &str| {
|
||||
log.lock().unwrap().push(name.to_string());
|
||||
Some(name != "Office PS")
|
||||
}));
|
||||
assert!(h.state_mut().execute("print.dialog"));
|
||||
let settle = |h: &mut Harness<'static, PdfCraftApp>, done: &dyn Fn(&PdfCraftApp) -> bool| {
|
||||
let deadline = std::time::Instant::now() + std::time::Duration::from_secs(10);
|
||||
while !done(h.state()) && std::time::Instant::now() < deadline {
|
||||
h.run_steps(1);
|
||||
std::thread::sleep(std::time::Duration::from_millis(5));
|
||||
}
|
||||
h.run_steps(2);
|
||||
};
|
||||
settle(&mut h, &|a| a.print_draft.two_sided.contains_key("Office PCL"));
|
||||
assert_eq!(h.state().print_draft.printers.len(), 3);
|
||||
let note = "doesn't take two-sided settings";
|
||||
assert!(h.query_by_label_contains(note).is_none(), "a driver that prints two-sided as asked");
|
||||
h.state_mut().print_draft.printer = Some("Office PS".into());
|
||||
settle(&mut h, &|a| a.print_draft.two_sided.contains_key("Office PS"));
|
||||
h.get_by_label_contains(note);
|
||||
assert_eq!(*asked.lock().unwrap(), ["Office PCL", "Office PS"], "each picked printer once, never the others");
|
||||
}
|
||||
|
||||
/// A driver Windows reports without two-sided printing keeps its own setting whatever Two-sided
|
||||
/// says (some print both sides with it Off), so the dialog says where to change it.
|
||||
#[test]
|
||||
@@ -545,6 +585,92 @@ fn a_printer_without_two_sided_printing_points_to_its_properties() {
|
||||
h.get_by_label_contains(note);
|
||||
}
|
||||
|
||||
/// A list that already says which drivers take two-sided settings is used as it is: no driver is
|
||||
/// asked again, whichever printer is picked.
|
||||
#[test]
|
||||
fn a_listed_two_sided_answer_is_not_asked_again() {
|
||||
use pdfcraft_engine::print::spool::Printer;
|
||||
use std::sync::{Arc, Mutex};
|
||||
let mut h = harness();
|
||||
h.state_mut().printer_source = Some(Arc::new(|| {
|
||||
vec![
|
||||
Printer { name: "Office PCL".into(), default: true, two_sided: Some(true), description: String::new() },
|
||||
Printer { name: "Office PS".into(), default: false, two_sided: Some(false), description: String::new() },
|
||||
]
|
||||
}));
|
||||
let asked = Arc::new(Mutex::new(Vec::<String>::new()));
|
||||
let log = asked.clone();
|
||||
h.state_mut().two_sided_source = Some(Arc::new(move |name: &str| {
|
||||
log.lock().unwrap().push(name.to_string());
|
||||
None
|
||||
}));
|
||||
assert!(h.state_mut().execute("print.dialog"));
|
||||
let deadline = std::time::Instant::now() + std::time::Duration::from_secs(10);
|
||||
while h.state().print_draft.printers.is_empty() && std::time::Instant::now() < deadline {
|
||||
h.run_steps(1);
|
||||
std::thread::sleep(std::time::Duration::from_millis(5));
|
||||
}
|
||||
h.run_steps(2);
|
||||
h.state_mut().print_draft.printer = Some("Office PS".into());
|
||||
h.run_steps(4);
|
||||
h.get_by_label_contains("doesn't take two-sided settings");
|
||||
assert!(asked.lock().unwrap().is_empty(), "the list's answers are used: {:?}", asked.lock().unwrap());
|
||||
}
|
||||
|
||||
/// Reads that hang (printers Windows can't reach) don't pile up as the user picks printer after
|
||||
/// printer: at most four run at once, and the printer picked is asked once one finishes.
|
||||
#[test]
|
||||
fn hanging_two_sided_reads_are_capped() {
|
||||
use pdfcraft_engine::print::spool::Printer;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::{Arc, Mutex};
|
||||
let names: Vec<String> = (1..=6).map(|i| format!("Away WSD {i}")).collect();
|
||||
let listed = names.clone();
|
||||
let mut h = harness();
|
||||
h.state_mut().printer_source = Some(Arc::new(move || {
|
||||
listed.iter().enumerate().map(|(i, n)| Printer { name: n.clone(), default: i == 0, two_sided: None, description: String::new() }).collect()
|
||||
}));
|
||||
let asked = Arc::new(Mutex::new(Vec::<String>::new()));
|
||||
let released = Arc::new(AtomicBool::new(false));
|
||||
let (log, gate) = (asked.clone(), released.clone());
|
||||
h.state_mut().two_sided_source = Some(Arc::new(move |name: &str| {
|
||||
log.lock().unwrap().push(name.to_string());
|
||||
// Hold the read like an unreachable printer, until the test lets it go (10 s at most).
|
||||
let deadline = std::time::Instant::now() + std::time::Duration::from_secs(10);
|
||||
while !gate.load(Ordering::SeqCst) && std::time::Instant::now() < deadline {
|
||||
std::thread::sleep(std::time::Duration::from_millis(5));
|
||||
}
|
||||
Some(true)
|
||||
}));
|
||||
assert!(h.state_mut().execute("print.dialog"));
|
||||
let deadline = std::time::Instant::now() + std::time::Duration::from_secs(10);
|
||||
while h.state().print_draft.printers.is_empty() && std::time::Instant::now() < deadline {
|
||||
h.run_steps(1);
|
||||
std::thread::sleep(std::time::Duration::from_millis(5));
|
||||
}
|
||||
for name in &names {
|
||||
h.state_mut().print_draft.printer = Some(name.clone());
|
||||
h.run_steps(2);
|
||||
}
|
||||
let started = |a: &Arc<Mutex<Vec<String>>>| a.lock().unwrap().len();
|
||||
let deadline = std::time::Instant::now() + std::time::Duration::from_secs(10);
|
||||
while started(&asked) < 4 && std::time::Instant::now() < deadline {
|
||||
std::thread::sleep(std::time::Duration::from_millis(5));
|
||||
}
|
||||
h.run_steps(2);
|
||||
let mut first = asked.lock().unwrap().clone();
|
||||
first.sort();
|
||||
assert_eq!(first, names[..4], "four reads at once, the rest wait");
|
||||
released.store(true, Ordering::SeqCst);
|
||||
let deadline = std::time::Instant::now() + std::time::Duration::from_secs(10);
|
||||
while !h.state().print_draft.two_sided.contains_key(&names[5]) && std::time::Instant::now() < deadline {
|
||||
h.run_steps(1);
|
||||
std::thread::sleep(std::time::Duration::from_millis(5));
|
||||
}
|
||||
assert_eq!(h.state().print_draft.two_sided.get(&names[5]), Some(&Some(true)), "the printer picked is asked once a read finishes");
|
||||
assert!(!h.state().print_draft.two_sided.contains_key(&names[4]), "a printer no longer picked isn't asked");
|
||||
}
|
||||
|
||||
/// Print as image (Acrobat's Advanced Print Setup) is a choice in the Print dialog's Advanced
|
||||
/// section: the saved print-ready PDF carries the page as a picture, no text.
|
||||
#[test]
|
||||
|
||||
Reference in new issue
Block a user