Module: Metaclean::Qpdf
- Defined in:
- lib/metaclean/qpdf.rb
Class Method Summary collapse
- .available? ⇒ Boolean
-
.rebuild!(path) ⇒ Object
Rebuilds a PDF in place.
- .version ⇒ Object
Class Method Details
.available? ⇒ Boolean
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
# File 'lib/metaclean/qpdf.rb', line 19 def available? return @available if defined?(@available) out, _err, status = Open3.capture3('qpdf', '--version') @available = status.success? # `qpdf --version` prints "qpdf version 11.9.0" on its first line. We # keep just the bare number (`.split.last`) so callers don't each have # to post-process it — matching Exiftool.version / Mat2.version. Captured # here so `version` reuses it instead of re-spawning the binary. @version = @available ? out.lines.first.to_s.strip.split.last : nil @available rescue Errno::ENOENT @version = nil @available = false end |
.rebuild!(path) ⇒ Object
Rebuilds a PDF in place. The qpdf flags here:
--linearize → optimize for streaming/web
--object-streams=generate → bundle objects efficiently
--remove-unreferenced-resources=yes → drop unused content (the
privacy-relevant part!)
qpdf can’t write back to the same file, so we use the standard “atomic write” pattern: write to a temp file, then rename it on top of the original. ‘File.rename` (used internally by `FileUtils.mv` for same-filesystem moves) is atomic on POSIX — either the swap completes or nothing changes. No “half-written” state is ever visible.
50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 |
# File 'lib/metaclean/qpdf.rb', line 50 def rebuild!(path) raise Error, 'qpdf not available' unless available? src = Metaclean.safe_path(path) # SecureRandom (not PID alone) makes the temp name unpredictable, so a # hostile process sharing the directory can't pre-create the path as a # symlink that qpdf would then write through. tmp = "#{src}.qpdf.tmp.#{SecureRandom.hex(8)}" _out, err, status = Open3.capture3( 'qpdf', '--linearize', '--object-streams=generate', '--remove-unreferenced-resources=yes', src, tmp ) # qpdf has a quirk: exit code 3 means "succeeded with warnings" (output # is still produced and valid). We treat that the same as success. success = status.success? || status.exitstatus == 3 raise Error, "qpdf failed: #{err.strip}" unless success FileUtils.mv(tmp, src) true ensure # Interrupt-safety: drop the temp if we died (or failed) before the # rename. On success it's already moved, so this is a no-op. File.delete(tmp) if tmp && File.exist?(tmp) end |
.version ⇒ Object
35 36 37 |
# File 'lib/metaclean/qpdf.rb', line 35 def version available? ? @version : nil end |