diff --git a/src/main/java/com/ffii/core/utils/PrinterLockManager.kt b/src/main/java/com/ffii/core/utils/PrinterLockManager.kt new file mode 100644 index 0000000..cf2fc8f --- /dev/null +++ b/src/main/java/com/ffii/core/utils/PrinterLockManager.kt @@ -0,0 +1,26 @@ +package com.ffii.core.utils + +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.locks.ReentrantLock + +/** + * Single-server printer lock manager. + * + * Keyed by "ip:port" so all print jobs targeting the same printer are serialized, + * preventing interleaving outputs when multiple users print concurrently. + */ +object PrinterLockManager { + private val locks = ConcurrentHashMap() + + fun withLock(printerIp: String, printerPort: Int, block: () -> T): T { + val key = "${printerIp.trim()}:$printerPort" + val lock = locks.computeIfAbsent(key) { ReentrantLock(true) } // fair lock + lock.lock() + try { + return block() + } finally { + lock.unlock() + } + } +} + diff --git a/src/main/java/com/ffii/core/utils/ZebraPrinterUtil.kt b/src/main/java/com/ffii/core/utils/ZebraPrinterUtil.kt index fbefae4..7b9ac36 100644 --- a/src/main/java/com/ffii/core/utils/ZebraPrinterUtil.kt +++ b/src/main/java/com/ffii/core/utils/ZebraPrinterUtil.kt @@ -7,6 +7,7 @@ import java.awt.Graphics2D import java.awt.image.BufferedImage import java.io.File import java.io.OutputStream +import java.net.InetSocketAddress import java.net.Socket import java.util.* @@ -42,39 +43,51 @@ open class ZebraPrinterUtil { throw IllegalArgumentException("Error: File not found or not readable at path: ${pdfFile.absolutePath}") } val renderDpi = dpi ?: 203 + val safeIp = printerIp.trim() + if (safeIp.isEmpty()) throw IllegalArgumentException("Error: Printer IP is blank") + try { - // 1. Load the PDF document - PDDocument.load(pdfFile).use { document -> - val renderer = PDFRenderer(document) - val totalPages = document.numberOfPages - - println("DEBUG: PDF has $totalPages pages") - - // Print each page for the specified quantity - repeat(printQty ?: 1) { copyIndex -> - println("DEBUG: Printing copy ${copyIndex + 1} of ${printQty ?: 1}") - - // Iterate through all pages in the PDF - for (pageIndex in 0 until totalPages) { - println("DEBUG: Processing page ${pageIndex + 1} of $totalPages") - - // 2. Render each page of the PDF as a monochrome image - val image = renderer.renderImage(pageIndex, renderDpi / 72f, ImageType.BINARY) - - // 3. Convert the image to a ZPL format string - val zplCommand = convertImageToZpl(image, printDirection) - - // 4. Send each page as a separate print job - Socket(printerIp, printerPort).use { socket -> - val os: OutputStream = socket.getOutputStream() - val printData = zplCommand.toByteArray() - os.write(printData) - os.flush() - println("DEBUG: Page ${pageIndex + 1} sent to printer") + // Serialize all jobs per printer to prevent interleaving. + PrinterLockManager.withLock(safeIp, printerPort) { + // 1. Load the PDF document + PDDocument.load(pdfFile).use { document -> + val renderer = PDFRenderer(document) + val totalPages = document.numberOfPages + + println("DEBUG: PDF has $totalPages pages") + + // 2. Open ONE connection and stream the whole job. + Socket().use { socket -> + socket.tcpNoDelay = true + socket.connect(InetSocketAddress(safeIp, printerPort), 5_000) + + val os: OutputStream = socket.getOutputStream() + val copies = printQty ?: 1 + + // Print each page for the specified quantity + repeat(copies.coerceAtLeast(1)) { copyIndex -> + println("DEBUG: Printing copy ${copyIndex + 1} of ${copies.coerceAtLeast(1)}") + + // Iterate through all pages in the PDF + for (pageIndex in 0 until totalPages) { + println("DEBUG: Processing page ${pageIndex + 1} of $totalPages") + + // Render each page of the PDF as a monochrome image + val image = renderer.renderImage(pageIndex, renderDpi / 72f, ImageType.BINARY) + + // Convert the image to a ZPL format string (contains ^XA ... ^XZ) + val zplCommand = convertImageToZpl(image, printDirection) + + // Send to printer (same socket for whole job) + os.write(zplCommand.toByteArray(Charsets.US_ASCII)) + + // Small delay between pages to ensure printer can process each page + Thread.sleep(100) + } } - // Small delay between pages to ensure printer can process each page - Thread.sleep(100) + os.flush() + println("DEBUG: Print job sent to printer ($safeIp:$printerPort)") } } }