From 198563aabac6b13764e0ed0e4a7218f916b393e9 Mon Sep 17 00:00:00 2001 From: Scheianu Ionut Date: Wed, 24 Sep 2025 21:42:22 +0300 Subject: [PATCH] updated to silent print --- py_app/PRINT_SERVICE_SETUP.md | 74 ++++ py_app/analyze_pdf_dimensions.py | 125 ++++++ py_app/app/__pycache__/routes.cpython-312.pyc | Bin 83229 -> 77590 bytes py_app/app/print_config.py | 16 + py_app/app/routes.py | 152 +------ py_app/app/templates/print_module.html | 169 ++++---- py_app/requirements.txt | 3 +- py_app/test_print_connection.py | 161 ++++++++ windows_print_service/CLEANUP_COMMANDS.sh | 18 + windows_print_service/INSTALLATION_GUIDE.md | 361 ----------------- .../NATIVE_SOLUTION_SUMMARY.md | 167 -------- windows_print_service/QUICK_SETUP.md | 69 ---- windows_print_service/QUICK_SETUP_NATIVE.md | 187 --------- .../chrome_extension/content.js | 26 +- .../chrome_extension/icons/README.md | 7 + .../chrome_extension/icons/create_icons.py | 27 ++ .../chrome_extension/icons/generate_icons.py | 122 ++++++ .../chrome_extension/icons/icon128.png | Bin 0 -> 545 bytes .../chrome_extension/icons/icon128.txt | 4 + .../chrome_extension/icons/icon16.png | Bin 0 -> 207 bytes .../chrome_extension/icons/icon16.txt | 4 + .../chrome_extension/icons/icon32.png | Bin 0 -> 235 bytes .../chrome_extension/icons/icon48.png | Bin 0 -> 285 bytes .../chrome_extension/icons/icon48.txt | 4 + windows_print_service/print_service.py | 374 ------------------ windows_print_service/service_manager.py | 143 ------- 26 files changed, 693 insertions(+), 1520 deletions(-) create mode 100644 py_app/PRINT_SERVICE_SETUP.md create mode 100644 py_app/analyze_pdf_dimensions.py create mode 100644 py_app/app/print_config.py create mode 100644 py_app/test_print_connection.py create mode 100644 windows_print_service/CLEANUP_COMMANDS.sh delete mode 100644 windows_print_service/INSTALLATION_GUIDE.md delete mode 100644 windows_print_service/NATIVE_SOLUTION_SUMMARY.md delete mode 100644 windows_print_service/QUICK_SETUP.md delete mode 100644 windows_print_service/QUICK_SETUP_NATIVE.md create mode 100644 windows_print_service/chrome_extension/icons/README.md create mode 100644 windows_print_service/chrome_extension/icons/create_icons.py create mode 100644 windows_print_service/chrome_extension/icons/generate_icons.py create mode 100644 windows_print_service/chrome_extension/icons/icon128.png create mode 100644 windows_print_service/chrome_extension/icons/icon128.txt create mode 100644 windows_print_service/chrome_extension/icons/icon16.png create mode 100644 windows_print_service/chrome_extension/icons/icon16.txt create mode 100644 windows_print_service/chrome_extension/icons/icon32.png create mode 100644 windows_print_service/chrome_extension/icons/icon48.png create mode 100644 windows_print_service/chrome_extension/icons/icon48.txt delete mode 100644 windows_print_service/print_service.py delete mode 100644 windows_print_service/service_manager.py diff --git a/py_app/PRINT_SERVICE_SETUP.md b/py_app/PRINT_SERVICE_SETUP.md new file mode 100644 index 0000000..9a31eff --- /dev/null +++ b/py_app/PRINT_SERVICE_SETUP.md @@ -0,0 +1,74 @@ +# Windows Print Service Network Configuration Guide + +## Current Issue +Your Flask server (Linux) cannot connect to Windows Print Service because they're on different machines. + +## Solution Options + +### Option 1: Same Machine Setup +If both Flask and Windows service are on the same machine: +```python +WINDOWS_PRINT_SERVICE_URL = "http://localhost:8765" +``` + +### Option 2: Different Machines (Recommended) +If Flask server (Linux) and Windows service are on different machines: + +1. **Find Windows Machine IP Address:** + ```cmd + # On Windows machine, run: + ipconfig + # Look for IPv4 Address, e.g., 192.168.1.100 + ``` + +2. **Update Configuration:** + ```python + # In /app/print_config.py, change: + WINDOWS_PRINT_SERVICE_URL = "http://192.168.1.100:8765" + ``` + +3. **Test Connection:** + ```bash + # From Linux machine, test: + curl http://192.168.1.100:8765/health + ``` + +### Option 3: Windows Firewall Configuration +Ensure Windows allows incoming connections on port 8765: + +1. **Windows Firewall Settings:** + - Control Panel → System and Security → Windows Defender Firewall + - Advanced Settings → Inbound Rules → New Rule + - Port → TCP → 8765 → Allow the connection + +2. **Test from Linux:** + ```bash + telnet [WINDOWS_IP] 8765 + ``` + +### Option 4: Port Forwarding/Tunneling +If direct connection isn't possible, set up SSH tunnel or port forwarding. + +## Quick Fix for Testing + +1. **Get Windows IP Address** +2. **Update print_config.py with the IP** +3. **Restart Flask server** +4. **Test printing** + +## Current Status +- ✅ Windows Print Service: Running (localhost:8765/health works) +- ❌ Network Connection: Flask can't reach Windows service +- 🔧 Fix Needed: Update WINDOWS_PRINT_SERVICE_URL with correct IP + +## Example Commands + +```bash +# 1. Find Windows IP (run on Windows): +# ipconfig | findstr IPv4 + +# 2. Test connection (run on Linux): +# curl http://[WINDOWS_IP]:8765/health + +# 3. Update config and restart Flask +``` \ No newline at end of file diff --git a/py_app/analyze_pdf_dimensions.py b/py_app/analyze_pdf_dimensions.py new file mode 100644 index 0000000..4a5a45d --- /dev/null +++ b/py_app/analyze_pdf_dimensions.py @@ -0,0 +1,125 @@ +#!/usr/bin/env python3 +""" +PDF Label Dimension Analysis Tool +Analyzes the generated PDF to verify exact dimensions and content positioning +""" + +import sys +import os +sys.path.append(os.path.join(os.path.dirname(__file__), 'app')) + +from app.pdf_generator import LabelPDFGenerator, mm_to_points +from reportlab.lib.units import mm + +def analyze_pdf_dimensions(): + """Analyze the PDF dimensions in detail""" + print("=== PDF Label Dimension Analysis ===\n") + + generator = LabelPDFGenerator() + + # Label dimensions + print("1. LABEL PHYSICAL DIMENSIONS:") + print(f" Width: {generator.label_width/mm:.1f}mm ({generator.label_width:.2f} points)") + print(f" Height: {generator.label_height/mm:.1f}mm ({generator.label_height:.2f} points)") + print(f" ✓ Should be exactly 80.0mm × 110.0mm") + + # Content area + print(f"\n2. CONTENT AREA DIMENSIONS:") + print(f" Content Width: {generator.content_width/mm:.1f}mm") + print(f" Content Height: {generator.content_height/mm:.1f}mm") + print(f" Left margin: {generator.content_x/mm:.1f}mm") + print(f" Bottom margin: {generator.content_y/mm:.1f}mm") + print(f" Right margin: {(generator.label_width - generator.content_x - generator.content_width)/mm:.1f}mm") + print(f" Top margin: {(generator.label_height - generator.content_y - generator.content_height)/mm:.1f}mm") + + # Row analysis + print(f"\n3. ROW LAYOUT:") + total_rows = 9 + standard_rows = 8 # 7 standard + 1 double + double_rows = 1 + + standard_row_height_mm = generator.row_height/mm + double_row_height_mm = generator.double_row_height/mm + + total_content_used = (standard_rows * standard_row_height_mm + double_rows * double_row_height_mm) + expected_content_height = generator.content_height/mm + + print(f" Standard row height: {standard_row_height_mm:.1f}mm") + print(f" Double row height: {double_row_height_mm:.1f}mm") + print(f" Total rows: {total_rows} (8 standard + 1 double)") + print(f" Content height used: {total_content_used:.1f}mm") + print(f" Content area available: {expected_content_height:.1f}mm") + print(f" Remaining space: {expected_content_height - total_content_used:.1f}mm") + + # Barcode area + print(f"\n4. BARCODE AREA:") + barcode_bottom_margin = generator.content_y/mm + print(f" Bottom barcode space: {barcode_bottom_margin:.1f}mm (should be ~12-15mm)") + + top_margin = (generator.label_height - generator.content_y - generator.content_height)/mm + print(f" Top margin space: {top_margin:.1f}mm") + + # Check for potential issues + print(f"\n5. POTENTIAL ISSUES ANALYSIS:") + + # Check if content is too high + if total_content_used > expected_content_height: + print(f" ⚠️ ISSUE: Content ({total_content_used:.1f}mm) exceeds available space ({expected_content_height:.1f}mm)") + else: + print(f" ✓ Content fits within available space") + + # Check margins + if barcode_bottom_margin < 10: + print(f" ⚠️ WARNING: Bottom margin ({barcode_bottom_margin:.1f}mm) may be too small for barcode") + else: + print(f" ✓ Bottom margin adequate for barcode") + + if top_margin < 3: + print(f" ⚠️ WARNING: Top margin ({top_margin:.1f}mm) very small") + else: + print(f" ✓ Top margin adequate") + + # Aspect ratio check + aspect_ratio = generator.label_height / generator.label_width + expected_ratio = 110.0 / 80.0 # 1.375 + + print(f"\n6. ASPECT RATIO CHECK:") + print(f" Current ratio: {aspect_ratio:.3f}") + print(f" Expected ratio: {expected_ratio:.3f} (110/80)") + + if abs(aspect_ratio - expected_ratio) < 0.001: + print(f" ✓ Aspect ratio is correct") + else: + print(f" ⚠️ Aspect ratio mismatch!") + + # Browser display notes + print(f"\n7. BROWSER DISPLAY CONSIDERATIONS:") + print(f" • Browser zoom affects apparent size") + print(f" • PDF viewer scaling can change appearance") + print(f" • Monitor DPI affects screen dimensions") + print(f" • Print preview scaling may alter appearance") + print(f"\n 🔧 SOLUTION: Always verify with physical printout at 100% scale") + + return { + 'width_mm': generator.label_width/mm, + 'height_mm': generator.label_height/mm, + 'content_height_mm': expected_content_height, + 'content_used_mm': total_content_used, + 'aspect_ratio': aspect_ratio, + 'expected_ratio': expected_ratio + } + +if __name__ == "__main__": + results = analyze_pdf_dimensions() + + print(f"\n=== SUMMARY ===") + if (abs(results['width_mm'] - 80.0) < 0.1 and + abs(results['height_mm'] - 110.0) < 0.1 and + abs(results['aspect_ratio'] - results['expected_ratio']) < 0.001): + print("✅ PDF dimensions are CORRECT: 80mm × 110mm") + print("If labels appear too large, check:") + print(" • Browser zoom level (should be 100%)") + print(" • PDF viewer scaling settings") + print(" • Print scaling (should be 'Actual size' or 100%)") + else: + print("❌ PDF dimensions need adjustment") \ No newline at end of file diff --git a/py_app/app/__pycache__/routes.cpython-312.pyc b/py_app/app/__pycache__/routes.cpython-312.pyc index 0e60eaab7e25b580ee10156a2898ddf8225637e4..9ca0ddc8a76d6a7fdbb251946ed775396c777c09 100644 GIT binary patch delta 457 zcmYMvy-NaN90&0Go_gt-mqnOoRGfI)SyDt;&>KzR;v6)z6f_vYl|dRbv_(rx55119 z5e@f8w1rC$4lNB0y-XtX@_cXU89w~p;kh~roi>BULoB8%@*Ni&8z1o#BZ>37+=U+8 zgR^_s*1fil>j)h0tQVI7)I>3fM(P`i;{K2lS(f|mJyAr)#8Ujf@3$0DH?fbj*D~>_ z9sF_!szfEXOap$(6G28HvdUCr)Iq}JnY9R`s9dWB%43VJ&NRU*QAV94Mp_&;7%A*_ zF=`?tjjN($p}(i>CRpqvNn(n+g*;N?#zHwYz>@!k9iFO?Dj5_;8so@#3u(NlT8?vB zk+WgJ5hWWUI?U2284+(bY!pOy7DKAD;yuma71L?;*lYj~NA|}ufpy4YJ5?EpDmYF;U92_S+I|SV4 zdetT^OqC$>G-m?Vw9>XzR9d=818M(IwL)w8dM4?xGTK7bZQA~sflRH6#&*xMA5NfZ zyQy=c^Ul5J+}}C(oOACx@24Ng&i;thUe;(-1pd7?l|GcMoYY#;wKu9ytCFM0;yTJ$ z4x-ns=Omy&5X|@wh;uoX_~y5cBhRt#Us9dFq(VPeqSwv`jOdIMeU_3~(Y*a3A_(^1 zK(+$ehgY$`1%gspE9%l08xVV{%Y|x@BgMMVPUK9z=|WaSrxsjj6{HPqOuo|V zNWQ$0OcwvbSghlSK_ucJf>R}5++@TSmC_>2^StrLc9jIL$#Ds;D58s!k8H^peGrM3 znBqZ6A90XiN?t{Ab+l5{idKspr{;=8TjHv?W{K|xPVHg=HegX~nH!XDIkfX4{Mj#=#XYkAsq6 z%HjsDn4@r+m^cD^{1kZ1IM2xA#w8J?IRoB>>#Vc@%%P!X(O}`3*OPEYrkrsH5mWJc zJP~?_72s+6-D-1=|hjDJ$Sv+n_Zs;j>nwKEeaOTHWnW2`A zj7RjA?ZCMsIpQ2jrJcx-Ffp|pC8p=mrt8j-;sPa8>)RCNQFj<~ow$Vm$qq%}y$uvbbipw~QSYkqYn(?7;IU^J2 znASnbfJBVT*HI~MB9Y4pIPgD&@=*kGsal$%ujpRl3l7paA(qU4=ic}e74x}uvkB5t zC5E0^BjzPHgYQpxzm~_XoOP%xiT0Ew*Zxwaku6PDYEq4_6ntt6S$VQrJ#DeBu0S}z z`lA85XVWvZKg`gP047Y34Gcs>$LZ(@{cJGIjEqO<9%umQz5shPI1r#$&`XiA* zgxdC2&`P`xG6f?7whmS?Hb?)PjD1?GO8h1MKhc ze9W9j+8YUm59aU!tdD-I(emhXtyZDS5*jR_x5~9u(8UOSl+Z%yz<#cCDhAE~!~{g> zoSq?IDsXHdFbc7XEa-c4 zb-G!cVru|)49HPTJT@rz1i`k0s|C*sP75Dpe+LiXu&`0!LO^I3UI4FDxdX}s2e5At zZJezr{&rbV2%!(Mxg5Z;6s{mRdJ7cDU}~XwbhYr^lh1Yk7#mtp302w;_XFz&L#G6f z!7lh7u_eE|!IYf-j-rZPjVBijIsIjyPV6?kuuqP)&1CAD4S#=ilp}NZW(n#26J-(p zi$A3HzfUzT$_c&Yrp}qxIp=h)8@lT0-U(U8Qkpt8d2CuctDLj=Ce$BO8eU&D%}s}S zZSy?Uau+XhrTWWJ9c^}``X~G6OjQ%A45dA(KB0a|o1xSvRVP$0sqg4)saLS!#67vx zumMdd7AeAPKjWS{I31g_ubwkCPN)QI_w=^uc3!(`o@&6@mQ}N=IrG|yqO6=S)K4ff zy5f`LC&o{{aN>ojgY&wojM_5Md2;)S?Nf@Giucss+cxJkH5HpFp7HT5Thn#h_}cA% zXyPqB6Y7k?lxm$!w5Hl8+ou&XjyXfi1cmpkEjd*)wP{Y{$`VLfGrcdPHlEyhV&~M_ zd3E(CQapuqlv-ZznW>qv^V+6)syQp$gp?c5ZMv0Lmt`rUtTIE_XY8dJ`%@W5MaEGz z{e0TdcvqvaYbUlXmJpf}-q^%9Kf^b5B`(JKlD+flZ{Q7C=~R3&o-tJLC6&|P=Jkyk zlQUzh<~?h9YkS6O&ngLP<$Z#*YA3cVY6XJnRIHqNK3%c)Zjrpge4mgzEZKIMW&_ITZKotD<)m_| zW?t_W@SA4$&2Hn(Prs+{SZp91?u@%8O0cAG8!qmC%Y2dIZCf*rT3kVQm83(96K~XIRYb8d zTSOX-iv+1LW=rzhPJBTkKD}jdeAzZ0W}Voh2U+Wvhw*daY9EW-Lwrcorh9~p^xlFS zqIuRo+sA9$<|$vQM~8bdAHJ`C)7ieegm}x_X~d69(vt0Kq?b%);OjfrAW1xRRR!X!8fEuN@~T2FR4@F#COYd?>t~->w){)mMwY%1k*BfE&^))2Stb?^YL1KNLlwy6JLc4WR zUPbrR$@tn%VNj>styb_&+T998T0&xRT0!EOv|0+h4j|Gc%3h~5UE0=bm)', methods=['POST']) -def print_labels_silent(order_id): - """Generate PDF and send directly to Windows Print Service - bypasses CORS""" - print(f"DEBUG: print_labels_silent called for order_id: {order_id}") - - if 'role' not in session or session['role'] not in ['superadmin', 'warehouse_manager', 'etichete']: - return jsonify({'error': 'Access denied'}), 403 - - try: - import requests - import tempfile - import os - from .pdf_generator import generate_order_labels_pdf, update_order_printed_status - from .print_module import get_db_connection - - # Get printer name from request - data = request.get_json() or {} - printer_name = data.get('printer_name', 'default') - - print(f"DEBUG: Using printer: {printer_name}") - - # Get order data from database - conn = get_db_connection() - cursor = conn.cursor() - - cursor.execute(""" - SELECT id, comanda_productie, cod_articol, descr_com_prod, cantitate, - data_livrare, dimensiune, com_achiz_client, nr_linie_com_client, customer_name, - customer_article_number, open_for_order, line_number, - printed_labels, created_at, updated_at - FROM order_for_labels - WHERE id = %s - """, (order_id,)) - - order_data = cursor.fetchone() - cursor.close() - conn.close() - - if not order_data: - return jsonify({'error': 'Order not found'}), 404 - - # Convert to dictionary for easier access - columns = ['id', 'comanda_productie', 'cod_articol', 'descr_com_prod', 'cantitate', - 'data_livrare', 'dimensiune', 'com_achiz_client', 'nr_linie_com_client', - 'customer_name', 'customer_article_number', 'open_for_order', 'line_number', - 'printed_labels', 'created_at', 'updated_at'] - order_dict = dict(zip(columns, order_data)) - - print(f"DEBUG: Order data: {order_dict}") - - # Generate PDF content in memory - pdf_content = generate_order_labels_pdf(order_dict) - - # Save PDF to a location accessible by Windows service - pdf_dir = r"C:\temp\quality_labels" - if not os.path.exists(pdf_dir): - os.makedirs(pdf_dir, exist_ok=True) - - pdf_filename = f"order_{order_id}_{order_dict['comanda_productie']}.pdf" - pdf_path = os.path.join(pdf_dir, pdf_filename) - - # Write PDF to file - with open(pdf_path, 'wb') as pdf_file: - pdf_file.write(pdf_content) - - print(f"DEBUG: Created PDF file: {pdf_path}") - - try: - # Send to Windows Print Service with local file path - print_service_url = 'http://localhost:8765' - - # Create the print request with local file path - print_data = { - 'pdf_path': pdf_path, - 'printer_name': printer_name, - 'copies': 1, - 'silent': True, - 'order_id': order_id, - 'quantity': order_dict['cantitate'] - } - - print(f"DEBUG: Sending print request to {print_service_url}/print/silent") - - # Send request to Windows service - response = requests.post( - f'{print_service_url}/print/silent', - json=print_data, - headers={'Content-Type': 'application/json'}, - timeout=30 - ) - - print(f"DEBUG: Print service response: {response.status_code}") - - if response.status_code == 200: - result = response.json() - print(f"DEBUG: Print result: {result}") - - if result.get('success'): - # Update order status - update_order_printed_status(order_id) - - return jsonify({ - 'success': True, - 'message': 'Labels printed successfully', - 'printer': printer_name, - 'order': order_dict['comanda_productie'], - 'quantity': order_dict['cantitate'] - }) - else: - return jsonify({ - 'success': False, - 'error': result.get('error', 'Print service returned error') - }), 500 - else: - return jsonify({ - 'success': False, - 'error': f'Print service returned status {response.status_code}' - }), 500 - - except requests.exceptions.RequestException as e: - print(f"DEBUG: Print service connection error: {e}") - # Fallback: Return the PDF for manual printing - return jsonify({ - 'success': False, - 'error': f'Windows Print Service not available: {str(e)}', - 'fallback': 'pdf_download', - 'pdf_path': pdf_path - }), 503 - - finally: - # Clean up PDF file - try: - os.unlink(pdf_path) - print(f"DEBUG: Cleaned up PDF file: {pdf_path}") - except Exception as e: - print(f"DEBUG: Error cleaning up PDF file: {e}") - - except Exception as e: - print(f"DEBUG: Error in print_labels_silent: {e}") - import traceback - traceback.print_exc() - return jsonify({'error': str(e)}), 500 \ No newline at end of file + +# NOTE for frontend/extension developers: +# To print labels, call the Chrome extension and pass the PDF URL: +# /generate_labels_pdf/ +# The extension should POST to http://localhost:8765/print/silent with: +# { +# "pdf_url": "https://your-linux-server/generate_labels_pdf/15", +# "printer_name": "default", +# "copies": 1 +# } \ No newline at end of file diff --git a/py_app/app/templates/print_module.html b/py_app/app/templates/print_module.html index 7b4ade3..8b21e4f 100644 --- a/py_app/app/templates/print_module.html +++ b/py_app/app/templates/print_module.html @@ -8,6 +8,17 @@ overflow: hidden; } +/* Inserted custom CSS from user */ +.card.scan-table-card table.print-module-table.scan-table thead th { + border-bottom: 2e6 !important; + background-color: #f8f9fa !important; + padding: 0.25rem 0.4rem !important; + text-align: left !important; + font-weight: 600 !important; + font-size: 10px !important; + line-height: 1.2 !important; +} + /* Enhanced table styling to match view_orders.html with higher specificity */ .card.scan-table-card table.print-module-table.scan-table { width: 100% !important; @@ -849,77 +860,101 @@ async function updatePrintedStatus(orderId) { } // PDF generation handler +// Helper to get extension ID injected by content script +function getInjectedExtensionId() { + const el = document.getElementById('chrome-extension-id'); + if (el) return el.getAttribute('data-extension-id'); + return null; +} + function addPDFGenerationHandler() { const printButton = document.getElementById('print-label-btn'); - - if (printButton) { - printButton.addEventListener('click', async function(e) { - e.preventDefault(); - - // Get selected order - const selectedRow = document.querySelector('.print-module-table tbody tr.selected'); - if (!selectedRow) { - alert('Please select an order first from the table below.'); - return; - } - - const orderId = selectedRow.dataset.orderId; - const prodOrder = selectedRow.querySelector('td:nth-child(2)').textContent.trim(); - const quantity = selectedRow.querySelector('td:nth-child(5)').textContent.trim(); - - if (!orderId) { - alert('Error: Could not determine order ID.'); - return; - } - - // Show loading state - const originalText = printButton.innerHTML; - const originalColor = printButton.style.background; - printButton.innerHTML = '⏳ Processing...'; - printButton.disabled = true; - + if (!printButton) return; + + printButton.addEventListener('click', async function(e) { + e.preventDefault(); + + // Get selected order + const selectedRow = document.querySelector('.print-module-table tbody tr.selected'); + if (!selectedRow) { + alert('Please select an order first from the table below.'); + return; + } + + const orderId = selectedRow.dataset.orderId; + const prodOrder = selectedRow.querySelector('td:nth-child(2)').textContent.trim(); + const quantity = selectedRow.querySelector('td:nth-child(5)').textContent.trim(); + if (!orderId) { + alert('Error: Could not determine order ID.'); + return; + } + + // Show loading state + const originalText = printButton.innerHTML; + const originalColor = printButton.style.background; + printButton.innerHTML = '⏳ Processing...'; + printButton.disabled = true; + + try { + // Step 1: Generate PDF and get its URL + const pdfResponse = await fetch(`/generate_labels_pdf/${orderId}`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' } + }); + if (!pdfResponse.ok) throw new Error('Failed to generate PDF'); + // Try to get the PDF URL from the response (assume server returns a URL or we can construct it) + // If not, fallback to download + let pdfUrl = ''; try { - let success = false; - - console.log(`🖨️ Print operation started - Service available: ${printServiceAvailable}`); - - // Try Windows service first if available - if (printServiceAvailable) { - console.log('🚀 Attempting silent print via Windows service...'); - try { - success = await printLabelsWithService(orderId, prodOrder, quantity); - console.log(`✅ Windows service print result: ${success}`); - } catch (serviceError) { - console.error('❌ Windows service failed:', serviceError); - console.warn('🔄 Falling back to PDF download mode'); - printServiceAvailable = false; // Mark as unavailable for this session - updatePrintButtonForService(false); - } - } else { - console.log('📄 Service not available, using PDF download mode'); - } - - // Fallback to PDF download if service failed or unavailable - if (!success) { - console.log('📥 Generating PDF for download...'); - success = await downloadPDFLabels(orderId, prodOrder, quantity); - console.log(`📄 PDF download result: ${success}`); - } - - } catch (error) { - console.error('Print operation failed:', error); - alert(`❌ Print operation failed: ${error.message}\n\nPlease check:\n• Windows Print Service is running\n• Chrome extension is installed\n• Network connectivity\n• PDF generation permissions`); - } finally { - // Reset button state - printButton.innerHTML = originalText; - printButton.style.background = originalColor; - printButton.disabled = false; - - // Recheck service availability for next operation - setTimeout(checkPrintServiceAvailability, 2000); + const data = await pdfResponse.json(); + pdfUrl = data.pdf_url || ''; + } catch { + // If not JSON, fallback to constructing the URL + pdfUrl = `/static/generated_labels/labels_${prodOrder}_${quantity}pcs.pdf`; } - }); - } + + // Step 2: Prepare print job for Chrome extension + const selectedPrinter = getSelectedPrinter(); + const printJob = { + pdfUrl: window.location.origin + pdfUrl, + printer: selectedPrinter, + orderId: orderId, + prodOrder: prodOrder, + quantity: quantity + }; + + // Step 3: Get extension ID from injected DOM + const extensionId = getInjectedExtensionId(); + + // Step 4: Send message to Chrome extension + if (window.chrome && window.chrome.runtime && window.chrome.runtime.sendMessage && extensionId) { + window.chrome.runtime.sendMessage( + extensionId, + { action: 'print_pdf', ...printJob }, + function(response) { + if (response && response.success) { + alert('✅ Labels sent to printer!\nOrder: ' + prodOrder + '\nQuantity: ' + quantity + '\nPrinter: ' + selectedPrinter); + updatePrintedStatus(orderId); + } else { + alert('❌ Failed to print via extension. PDF will be downloaded.'); + downloadPDFLabels(orderId, prodOrder, quantity); + } + } + ); + } else { + // Fallback: Download PDF + alert('ℹ️ Chrome extension not detected or extension ID not injected. PDF will be downloaded.'); + await downloadPDFLabels(orderId, prodOrder, quantity); + } + } catch (error) { + console.error('Print operation failed:', error); + alert('❌ Print operation failed: ' + error.message); + } finally { + printButton.innerHTML = originalText; + printButton.style.background = originalColor; + printButton.disabled = false; + } + }); } {% endblock %} \ No newline at end of file diff --git a/py_app/requirements.txt b/py_app/requirements.txt index 9a8a97c..12a78a8 100644 --- a/py_app/requirements.txt +++ b/py_app/requirements.txt @@ -5,4 +5,5 @@ gunicorn flask-sqlalchemy pyodbc mariadb -reportlab \ No newline at end of file +reportlab +requests \ No newline at end of file diff --git a/py_app/test_print_connection.py b/py_app/test_print_connection.py new file mode 100644 index 0000000..deb26e5 --- /dev/null +++ b/py_app/test_print_connection.py @@ -0,0 +1,161 @@ +#!/usr/bin/env python3 +""" +Windows Print Service Connection Tester +Tests connectivity between Flask server and Windows Print Service +""" + +import sys +import os +sys.path.append(os.path.join(os.path.dirname(__file__), 'app')) + +import requests +import time +from app.print_config import WINDOWS_PRINT_SERVICE_URL, PRINT_SERVICE_TIMEOUT + +def test_print_service_connection(): + """Test connection to Windows Print Service""" + print("=== Windows Print Service Connection Test ===\n") + + print(f"🔍 Testing connection to: {WINDOWS_PRINT_SERVICE_URL}") + print(f"⏱️ Timeout: {PRINT_SERVICE_TIMEOUT} seconds") + print(f"🌐 From: Flask server (Linux)") + print(f"🎯 To: Windows Print Service") + print() + + # Test 1: Health check + print("1️⃣ Testing /health endpoint...") + try: + start_time = time.time() + response = requests.get( + f"{WINDOWS_PRINT_SERVICE_URL}/health", + timeout=PRINT_SERVICE_TIMEOUT + ) + elapsed = time.time() - start_time + + if response.status_code == 200: + data = response.json() + print(f" ✅ SUCCESS (Status: {response.status_code}, Time: {elapsed:.2f}s)") + print(f" 📋 Service: {data.get('service', 'Unknown')}") + print(f" 🖥️ Platform: {data.get('platform', 'Unknown')}") + print(f" 📅 Timestamp: {data.get('timestamp', 'Unknown')}") + health_ok = True + else: + print(f" ❌ FAILED (Status: {response.status_code})") + print(f" 📝 Response: {response.text}") + health_ok = False + except requests.exceptions.ConnectionError as e: + print(f" ❌ CONNECTION REFUSED") + print(f" 🔧 Error: {str(e)}") + print(f" 💡 This usually means:") + print(f" • Windows service is not running") + print(f" • Wrong IP address in configuration") + print(f" • Firewall blocking connection") + print(f" • Network routing issue") + health_ok = False + except Exception as e: + print(f" ❌ ERROR: {str(e)}") + health_ok = False + + print() + + # Test 2: Printers endpoint (only if health check passed) + if health_ok: + print("2️⃣ Testing /printers endpoint...") + try: + response = requests.get( + f"{WINDOWS_PRINT_SERVICE_URL}/printers", + timeout=PRINT_SERVICE_TIMEOUT + ) + if response.status_code == 200: + data = response.json() + printers = data.get('printers', []) + print(f" ✅ SUCCESS - Found {len(printers)} printer(s)") + for i, printer in enumerate(printers[:3]): # Show first 3 + name = printer.get('name', 'Unknown') if isinstance(printer, dict) else printer + print(f" 🖨️ {i+1}. {name}") + if len(printers) > 3: + print(f" ... and {len(printers)-3} more") + else: + print(f" ❌ FAILED (Status: {response.status_code})") + except Exception as e: + print(f" ❌ ERROR: {str(e)}") + + print() + + # Test 3: Network diagnostics + print("3️⃣ Network Diagnostics...") + + # Parse URL to get host and port + try: + from urllib.parse import urlparse + parsed = urlparse(WINDOWS_PRINT_SERVICE_URL) + host = parsed.hostname + port = parsed.port or 8765 + + print(f" 🌐 Target Host: {host}") + print(f" 🔌 Target Port: {port}") + + # Test DNS resolution + try: + import socket + ip = socket.gethostbyname(host) + print(f" 🔍 DNS Resolution: {host} → {ip}") + dns_ok = True + except Exception as e: + print(f" ❌ DNS Resolution failed: {e}") + dns_ok = False + + # Test raw TCP connection + if dns_ok: + try: + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.settimeout(5) + result = sock.connect_ex((host, port)) + sock.close() + + if result == 0: + print(f" ✅ TCP Connection: Port {port} is open") + else: + print(f" ❌ TCP Connection: Port {port} is closed or filtered") + print(f" 💡 Check Windows Firewall and service status") + except Exception as e: + print(f" ❌ TCP Test error: {e}") + except Exception as e: + print(f" ❌ URL parsing error: {e}") + + print() + + # Final recommendations + print("🔧 TROUBLESHOOTING RECOMMENDATIONS:") + print() + + if not health_ok: + print("❌ CONNECTION FAILED - Try these steps:") + print("1. Verify Windows Print Service is running:") + print(" • Windows: sc query QualityLabelPrinting") + print(" • Or: Get-Service QualityLabelPrinting") + print() + print("2. Check Windows machine IP address:") + print(" • Windows: ipconfig | findstr IPv4") + print(" • Update WINDOWS_PRINT_SERVICE_URL in print_config.py") + print() + print("3. Test firewall:") + print(" • Windows: Allow port 8765 in Windows Firewall") + print(" • Test: telnet [WINDOWS_IP] 8765") + print() + print("4. If same machine, verify service binding:") + print(" • Service should bind to 0.0.0.0:8765 (not 127.0.0.1)") + else: + print("✅ CONNECTION SUCCESS - Print service should work!") + print("• Service is reachable and responding") + print("• Ready for label printing") + print("• Try printing labels from the web interface") + + print(f"\n📝 Current Configuration:") + print(f" Service URL: {WINDOWS_PRINT_SERVICE_URL}") + print(f" Timeout: {PRINT_SERVICE_TIMEOUT}s") + + return health_ok + +if __name__ == "__main__": + test_print_service_connection() \ No newline at end of file diff --git a/windows_print_service/CLEANUP_COMMANDS.sh b/windows_print_service/CLEANUP_COMMANDS.sh new file mode 100644 index 0000000..acb6f49 --- /dev/null +++ b/windows_print_service/CLEANUP_COMMANDS.sh @@ -0,0 +1,18 @@ +#!/bin/bash +# Cleanup script for Quality Label Printing Service folder +# Run this from the windows_print_service directory (in Git Bash, WSL, or Linux) + +# Remove Python-based service and management +rm -f print_service.py +rm -f service_manager.py + +# Remove extra documentation (keep only README.md) +rm -f INSTALLATION_GUIDE.md NATIVE_SOLUTION_SUMMARY.md QUICK_SETUP.md QUICK_SETUP_NATIVE.md + +# Optionally remove test_service.ps1 if not needed +# rm -f test_service.ps1 + +# Done +ls -l + +echo "Cleanup complete. Only PowerShell service, Chrome extension, and README remain." \ No newline at end of file diff --git a/windows_print_service/INSTALLATION_GUIDE.md b/windows_print_service/INSTALLATION_GUIDE.md deleted file mode 100644 index 0b68622..0000000 --- a/windows_print_service/INSTALLATION_GUIDE.md +++ /dev/null @@ -1,361 +0,0 @@ -# Quality Recticel Windows Print Service - Installation Guide - -## 📋 Overview - -The Quality Recticel Windows Print Service enables **silent PDF printing** directly from the web application through a Chrome extension. This system eliminates the need for manual PDF downloads and provides seamless label printing functionality. - -## 🏗️ System Architecture - -``` -Web Application (print_module.html) - ↓ -Windows Print Service (localhost:8765) - ↓ -Chrome Extension (Native Messaging) - ↓ -Windows Print System -``` - -## 📦 Package Contents - -``` -windows_print_service/ -├── print_service.py # Main Windows service (Flask API) -├── service_manager.py # Service installation & management -├── install_service.bat # Automated installation script -├── chrome_extension/ # Chrome extension files -│ ├── manifest.json # Extension configuration -│ ├── background.js # Service worker -│ ├── content.js # Page integration -│ ├── popup.html # Extension UI -│ ├── popup.js # Extension logic -│ └── icons/ # Extension icons -└── INSTALLATION_GUIDE.md # This documentation -``` - -## 🔧 Prerequisites - -### System Requirements -- **Operating System**: Windows 10/11 (64-bit) -- **Python**: Python 3.8 or higher -- **Browser**: Google Chrome (latest version) -- **Privileges**: Administrator access required for installation - -### Python Dependencies -The following packages will be installed automatically: -- `flask` - Web service framework -- `flask-cors` - Cross-origin resource sharing -- `requests` - HTTP client library -- `pywin32` - Windows service integration - -## 🚀 Installation Process - -### Step 1: Download and Extract Files - -1. Download the `windows_print_service` folder to your system -2. Extract to a permanent location (e.g., `C:\QualityRecticel\PrintService\`) -3. **Do not move or delete this folder after installation** - -### Step 2: Install Windows Service - -#### Method A: Automated Installation (Recommended) - -1. **Right-click** on `install_service.bat` -2. Select **"Run as administrator"** -3. Click **"Yes"** when Windows UAC prompt appears -4. Wait for installation to complete - -#### Method B: Manual Installation - -If the automated script fails, follow these steps: - -```bash -# Open Command Prompt as Administrator -cd C:\path\to\windows_print_service - -# Install Python dependencies -pip install flask flask-cors requests pywin32 - -# Install Windows service -python service_manager.py install - -# Add firewall exception -netsh advfirewall firewall add rule name="Quality Recticel Print Service" dir=in action=allow protocol=TCP localport=8765 - -# Create Chrome extension registry entry -reg add "HKEY_CURRENT_USER\Software\Google\Chrome\NativeMessagingHosts\com.qualityrecticel.printservice" /ve /d "%cd%\chrome_extension\manifest.json" /f -``` - -### Step 3: Install Chrome Extension - -1. Open **Google Chrome** -2. Navigate to `chrome://extensions/` -3. Enable **"Developer mode"** (toggle in top-right corner) -4. Click **"Load unpacked"** -5. Select the `chrome_extension` folder -6. Verify the extension appears with a printer icon - -### Step 4: Verify Installation - -#### Check Windows Service Status - -1. Press `Win + R`, type `services.msc`, press Enter -2. Look for **"Quality Recticel Print Service"** -3. Status should show **"Running"** -4. Startup type should be **"Automatic"** - -#### Test API Endpoints - -Open a web browser and visit: -- **Health Check**: `http://localhost:8765/health` -- **Printer List**: `http://localhost:8765/printers` - -Expected response for health check: -```json -{ - "status": "healthy", - "service": "Quality Recticel Print Service", - "version": "1.0", - "timestamp": "2025-09-21T10:30:00" -} -``` - -#### Test Chrome Extension - -1. Click the extension icon in Chrome toolbar -2. Verify it shows "Service Status: Connected ✅" -3. Check that printers are listed -4. Try the "Test Print" button - -## 🔄 Web Application Integration - -The web application automatically detects the Windows service and adapts the user interface: - -### Service Available (Green Button) -- Button text: **"🖨️ Print Labels (Silent)"** -- Functionality: Direct printing to default printer -- User experience: Click → Labels print immediately - -### Service Unavailable (Blue Button) -- Button text: **"📄 Generate PDF"** -- Functionality: PDF download for manual printing -- User experience: Click → PDF downloads to browser - -### Detection Logic -```javascript -// Automatic service detection on page load -const response = await fetch('http://localhost:8765/health'); -if (response.ok) { - // Service available - enable silent printing -} else { - // Service unavailable - fallback to PDF download -} -``` - -## 🛠️ Configuration - -### Service Configuration - -The service runs with the following default settings: - -| Setting | Value | Description | -|---------|-------|-------------| -| **Port** | 8765 | Local API port | -| **Host** | localhost | Service binding | -| **Startup** | Automatic | Starts with Windows | -| **Printer** | Default | Uses system default printer | -| **Copies** | 1 | Default print copies | - -### Chrome Extension Permissions - -The extension requires these permissions: -- `printing` - Access to printer functionality -- `nativeMessaging` - Communication with Windows service -- `activeTab` - Access to current webpage -- `storage` - Save extension settings - -## 🔍 Troubleshooting - -### Common Issues - -#### 1. Service Not Starting -**Symptoms**: API not accessible at localhost:8765 -**Solutions**: -```bash -# Check service status -python -c "from service_manager import service_status; service_status()" - -# Restart service manually -python service_manager.py restart - -# Check Windows Event Viewer for service errors -``` - -#### 2. Chrome Extension Not Working -**Symptoms**: Extension shows "Service Status: Disconnected ❌" -**Solutions**: -- Verify Windows service is running -- Check firewall settings (port 8765 must be open) -- Reload the Chrome extension -- Restart Chrome browser - -#### 3. Firewall Blocking Connection -**Symptoms**: Service runs but web page can't connect -**Solutions**: -```bash -# Add firewall rule manually -netsh advfirewall firewall add rule name="Quality Recticel Print Service" dir=in action=allow protocol=TCP localport=8765 - -# Or disable Windows Firewall temporarily to test -``` - -#### 4. Permission Denied Errors -**Symptoms**: Installation fails with permission errors -**Solutions**: -- Ensure running as Administrator -- Check Windows UAC settings -- Verify Python installation permissions - -#### 5. Print Jobs Not Processing -**Symptoms**: API accepts requests but nothing prints -**Solutions**: -- Check default printer configuration -- Verify printer drivers are installed -- Test manual printing from other applications -- Check Windows Print Spooler service - -### Log Files - -Check these locations for troubleshooting: - -| Component | Log Location | -|-----------|--------------| -| **Windows Service** | `print_service.log` (same folder as service) | -| **Chrome Extension** | Chrome DevTools → Extensions → Background page | -| **Windows Event Log** | Event Viewer → Windows Logs → System | - -### Diagnostic Commands - -```bash -# Check service status -python service_manager.py status - -# Test API manually -curl http://localhost:8765/health - -# List available printers -curl http://localhost:8765/printers - -# Check Windows service -sc query QualityRecticelPrintService - -# Check listening ports -netstat -an | findstr :8765 -``` - -## 🔄 Maintenance - -### Updating the Service - -1. Stop the current service: -```bash -python service_manager.py stop -``` - -2. Replace service files with new versions - -3. Restart the service: -```bash -python service_manager.py start -``` - -### Uninstalling - -#### Remove Chrome Extension -1. Go to `chrome://extensions/` -2. Find "Quality Recticel Print Service" -3. Click "Remove" - -#### Remove Windows Service -```bash -# Run as Administrator -python service_manager.py uninstall -``` - -#### Remove Firewall Rule -```bash -netsh advfirewall firewall delete rule name="Quality Recticel Print Service" -``` - -## 📞 Support Information - -### API Endpoints Reference - -| Endpoint | Method | Purpose | -|----------|--------|---------| -| `/health` | GET | Service health check | -| `/printers` | GET | List available printers | -| `/print/pdf` | POST | Print PDF from URL | -| `/print/silent` | POST | Silent print with metadata | - -### Request Examples - -**Silent Print Request**: -```json -POST /print/silent -{ - "pdf_url": "http://localhost:5000/generate_labels_pdf/123", - "printer_name": "default", - "copies": 1, - "silent": true, - "order_id": "123", - "quantity": "10" -} -``` - -**Expected Response**: -```json -{ - "success": true, - "message": "Print job sent successfully", - "job_id": "print_20250921_103000", - "printer": "HP LaserJet Pro", - "timestamp": "2025-09-21T10:30:00" -} -``` - -## 📚 Technical Details - -### Service Architecture -- **Framework**: Flask (Python) -- **Service Type**: Windows Service (pywin32) -- **Communication**: HTTP REST API + Native Messaging -- **Security**: Localhost binding only (127.0.0.1:8765) - -### Chrome Extension Architecture -- **Manifest Version**: 3 -- **Service Worker**: Handles background print requests -- **Content Script**: Integrates with Quality Recticel web pages -- **Native Messaging**: Communicates with Windows service - -### Security Considerations -- Service only accepts local connections (localhost) -- No external network access required -- Chrome extension runs in sandboxed environment -- Windows service runs with system privileges (required for printing) - ---- - -## 📋 Quick Start Checklist - -- [ ] Download `windows_print_service` folder -- [ ] Right-click `install_service.bat` → "Run as administrator" -- [ ] Install Chrome extension from `chrome_extension` folder -- [ ] Verify service at `http://localhost:8765/health` -- [ ] Test printing from Quality Recticel web application - -**Installation Time**: ~5 minutes -**User Training Required**: Minimal (automatic detection and fallback) -**Maintenance**: Zero (auto-starts with Windows) - -For additional support, check the log files and diagnostic commands listed above. \ No newline at end of file diff --git a/windows_print_service/NATIVE_SOLUTION_SUMMARY.md b/windows_print_service/NATIVE_SOLUTION_SUMMARY.md deleted file mode 100644 index 8769283..0000000 --- a/windows_print_service/NATIVE_SOLUTION_SUMMARY.md +++ /dev/null @@ -1,167 +0,0 @@ -# Native Windows Print Service - Implementation Summary - -## 🎯 Solution Overview - -Successfully replaced the Python Flask-based Windows print service with a **native PowerShell implementation** that eliminates all external dependencies while maintaining full functionality. - -## 📁 Files Created/Modified - -### Core Service Files -- ✅ `print_service.ps1` - Complete PowerShell HTTP server with REST API -- ✅ `install_native_service.bat` - Native Windows service installer -- ✅ `uninstall_service.bat` - Service removal script - -### Documentation Updated -- ✅ `README.md` - Comprehensive native solution documentation -- ✅ `QUICK_SETUP_NATIVE.md` - Fast setup guide for native solution -- ✅ `routes.py` - Updated ZIP package to prioritize native files - -### Web Integration Updated -- ✅ `print_module.html` - Replaced PDF info card with printer dropdown -- ✅ Service detection and printer enumeration integration -- ✅ Enhanced error handling for native service endpoints - -## 🚀 Key Advantages - -| Feature | Python Flask | Native PowerShell | -|---------|-------------|------------------| -| **Dependencies** | Python + Flask + Requests + pip packages | PowerShell only (built-in) | -| **Installation Time** | 5-10 minutes | 1-2 minutes | -| **Startup Time** | 10-15 seconds | 2-3 seconds | -| **Memory Usage** | 50-100MB | 10-20MB | -| **Disk Space** | 200-500MB | 5-10MB | -| **Security** | External packages | Microsoft-signed only | -| **Enterprise Ready** | Requires IT approval | Uses trusted components | -| **Troubleshooting** | Python debugging | Native Windows tools | - -## 🔧 Technical Implementation - -### PowerShell HTTP Server -```powershell -# Uses .NET HttpListener for native HTTP serving -$listener = New-Object System.Net.HttpListener -$listener.Prefixes.Add("http://localhost:8765/") -``` - -### Printer Integration -```powershell -# Native WMI integration for printer enumeration -Get-WmiObject -Class Win32_Printer | Where-Object { $_.Local -eq $true } -``` - -### PDF Printing -```powershell -# Native file download and print via Windows shell -$webClient = New-Object System.Net.WebClient -Start-Process -FilePath $pdfFile -Verb Print -``` - -## 📡 API Endpoints (Maintained Compatibility) - -All original endpoints preserved: -- `GET /health` - Service health check -- `GET /printers` - List available printers -- `POST /print/pdf` - Print PDF documents -- `POST /print/silent` - Silent PDF printing - -## 🔄 Migration Path - -### For Existing Users -1. Run `uninstall_service.bat` to remove Python service -2. Run `install_native_service.bat` to install native service -3. No Chrome extension changes needed - same API endpoints - -### For New Deployments -1. Download updated service package (includes native solution) -2. Run `install_native_service.bat` as Administrator -3. Install Chrome extension as before -4. Everything works identically to Python version - -## 🛡️ Security & Compliance - -### Enterprise Benefits -- **No Third-Party Code**: Uses only Microsoft PowerShell and .NET -- **Reduced Attack Surface**: Fewer dependencies = fewer vulnerabilities -- **Audit Friendly**: All code visible and editable -- **Corporate Compliance**: Easier approval through IT security - -### Security Features -- Localhost-only binding (127.0.0.1) -- CORS headers for browser security -- Automatic temporary file cleanup -- Service-level isolation - -## 📊 Performance Metrics - -### Service Startup -- Python Flask: ~12 seconds (package imports, Flask initialization) -- Native PowerShell: ~2 seconds (PowerShell load only) - -### Memory Footprint -- Python Flask: ~60MB (Python runtime + packages) -- Native PowerShell: ~15MB (PowerShell host + .NET objects) - -### HTTP Response Time -- Both solutions: <50ms for API endpoints (no significant difference) - -## 🎉 Deployment Advantages - -### IT Department Benefits -1. **Single Installer**: One .bat file installs everything -2. **No Prerequisites**: Works on any Windows machine -3. **Easy Troubleshooting**: Native Windows service tools -4. **Clean Uninstall**: Complete removal with uninstall script -5. **Standard Logging**: Uses Windows event system integration -6. **Group Policy Compatible**: Can be deployed via GPO - -### End User Benefits -1. **Faster Installation**: 3 minutes vs 10+ minutes -2. **Better Reliability**: Fewer moving parts to break -3. **Lower Resource Usage**: Less CPU and RAM consumption -4. **Familiar Tools**: Standard Windows service management - -## 🔧 Maintenance & Support - -### Service Management -```batch -# All standard Windows service commands work -sc start QualityRecticelPrintService -sc stop QualityRecticelPrintService -sc query QualityRecticelPrintService -``` - -### Log Files -- **Location**: `C:\Program Files\QualityRecticel\PrintService\print_service.log` -- **Format**: Timestamp + message (human readable) -- **Rotation**: Automatic (PowerShell handles) - -### Configuration -- **Port**: Editable in print_service.ps1 (default 8765) -- **Logging**: Configurable verbosity levels -- **Service Account**: Standard Windows service account options - -## ✅ Success Metrics - -### Installation Success Rate -- Target: 95%+ first-time success -- Native solution eliminates dependency conflicts - -### Performance Improvement -- 80% faster startup time -- 70% less memory usage -- 95% smaller disk footprint - -### Support Ticket Reduction -- Fewer dependency-related issues -- Easier troubleshooting with native tools -- Better compatibility across Windows versions - -## 🚀 Next Steps - -1. **User Testing**: Deploy to pilot group for validation -2. **Documentation Updates**: Ensure all guides reflect native solution -3. **Package Distribution**: Update download system with native version -4. **Migration Support**: Help existing users transition from Python version -5. **Training Materials**: Create guides for IT support teams - -The native PowerShell solution provides all the functionality of the Python version while being significantly more enterprise-friendly, faster, and easier to deploy and maintain. \ No newline at end of file diff --git a/windows_print_service/QUICK_SETUP.md b/windows_print_service/QUICK_SETUP.md deleted file mode 100644 index e48da2b..0000000 --- a/windows_print_service/QUICK_SETUP.md +++ /dev/null @@ -1,69 +0,0 @@ -# 🚀 Quality Recticel Print Service - Quick Setup - -## 📦 What You Get -- **Silent PDF Printing** - No more manual downloads! -- **Automatic Detection** - Smart fallback when service unavailable -- **Zero Configuration** - Works out of the box - -## ⚡ 2-Minute Installation - -### Step 1: Install Windows Service -1. **Right-click** `install_service.bat` -2. Select **"Run as administrator"** -3. Click **"Yes"** and wait for completion - -### Step 2: Install Chrome Extension -1. Open Chrome → `chrome://extensions/` -2. Enable **"Developer mode"** -3. Click **"Load unpacked"** → Select `chrome_extension` folder - -### Step 3: Verify Installation -- Visit: `http://localhost:8765/health` -- Should see: `{"status": "healthy"}` - -## 🎯 How It Works - -| Service Status | Button Appearance | What Happens | -|---------------|-------------------|--------------| -| **Running** ✅ | 🖨️ **Print Labels (Silent)** (Green) | Direct printing | -| **Not Running** ❌ | 📄 **Generate PDF** (Blue) | PDF download | - -## ⚠️ Troubleshooting - -| Problem | Solution | -|---------|----------| -| **Service won't start** | Run `install_service.bat` as Administrator | -| **Chrome extension not working** | Reload extension in `chrome://extensions/` | -| **Can't connect to localhost:8765** | Check Windows Firewall (port 8765) | -| **Nothing prints** | Verify default printer is set up | - -## 🔧 Management Commands - -```bash -# Check service status -python service_manager.py status - -# Restart service -python service_manager.py restart - -# Uninstall service -python service_manager.py uninstall -``` - -## 📍 Important Notes - -- ⚡ **Auto-starts** with Windows - no manual intervention needed -- 🔒 **Local only** - service only accessible from same computer -- 🖨️ **Uses default printer** - configure your default printer in Windows -- 💾 **Don't move files** after installation - keep folder in same location - -## 🆘 Quick Support - -**Service API**: `http://localhost:8765` -**Health Check**: `http://localhost:8765/health` -**Printer List**: `http://localhost:8765/printers` - -**Log File**: `print_service.log` (same folder as installation) - ---- -*Installation takes ~5 minutes • Zero maintenance required • Works with existing Quality Recticel web application* \ No newline at end of file diff --git a/windows_print_service/QUICK_SETUP_NATIVE.md b/windows_print_service/QUICK_SETUP_NATIVE.md deleted file mode 100644 index b3f0980..0000000 --- a/windows_print_service/QUICK_SETUP_NATIVE.md +++ /dev/null @@ -1,187 +0,0 @@ -# Quality Recticel Print Service - Quick Setup Guide (Native) - -Get the Windows print service running in under 3 minutes - **Zero Dependencies!** - -## What You Need - -✅ Windows 10/11 or Windows Server -✅ Administrator access -✅ Chrome browser -✅ Downloaded service package - -## Installation Steps - -### 1. Install Native Windows Service (1 minute) - -```batch -# Extract service package to any folder -# Right-click install_native_service.bat -# Select "Run as administrator" -install_native_service.bat -``` - -**Expected Output:** -``` -✅ Administrator privileges confirmed -✅ Service directory created -✅ Service files copied successfully -✅ Windows service created successfully -✅ Service started successfully -🌐 Service running on http://localhost:8765 -``` - -### 2. Install Chrome Extension (1 minute) - -``` -1. Open Chrome → More Tools → Extensions -2. Enable "Developer mode" (top right) -3. Click "Load unpacked" -4. Select the chrome_extension folder -5. Extension installed ✅ -``` - -### 3. Test Everything (30 seconds) - -**Test Service:** -- Open browser: http://localhost:8765/health -- Should show: `{"status": "healthy", "service": "Quality Recticel Print Service", "platform": "Windows PowerShell"}` - -**Test Printing:** -- Go to Quality Recticel web app -- Open a label for printing -- Click print - should print silently ✅ - -## Native Advantages - -🚀 **No Python Required** - Pure PowerShell implementation -⚡ **Instant Install** - No package downloads or dependencies -🛡️ **Enterprise Ready** - Uses only Microsoft components -📦 **Tiny Footprint** - Minimal disk and memory usage -🔧 **Easy Deployment** - Single installer, works everywhere - -## Troubleshooting - -### ❌ Service Won't Install -```batch -# Check if already installed -sc query QualityRecticelPrintService - -# If exists, uninstall first -uninstall_service.bat - -# Try installation again -install_native_service.bat -``` - -### ❌ Service Won't Start -```powershell -# Check PowerShell execution policy -Get-ExecutionPolicy -# Should be RemoteSigned or Unrestricted - -# Set if needed (as Administrator) -Set-ExecutionPolicy RemoteSigned -Force - -# Check the logs -Get-Content "C:\Program Files\QualityRecticel\PrintService\print_service.log" -Tail 20 - -# Manual start -sc start QualityRecticelPrintService -``` - -### ❌ Port Already in Use -```cmd -# Check what's using port 8765 -netstat -ano | findstr :8765 - -# Kill process if needed (find PID from above) -taskkill /PID /F -``` - -### ❌ Chrome Extension Issues -1. Check extension is enabled in chrome://extensions/ -2. Test service URL directly: http://localhost:8765/health -3. Check browser console (F12) for errors -4. Verify CORS headers are working - -### ❌ Printing Doesn't Work -```bash -# Test printer enumeration -curl http://localhost:8765/printers - -# Check Windows print spooler -sc query spooler - -# Restart print spooler if needed -sc stop spooler && sc start spooler -``` - -## Quick Commands - -**Service Management:** -```batch -sc start QualityRecticelPrintService # Start -sc stop QualityRecticelPrintService # Stop -sc query QualityRecticelPrintService # Status -sc qc QualityRecticelPrintService # Configuration -``` - -**Test Endpoints:** -```bash -curl http://localhost:8765/health # Health check -curl http://localhost:8765/printers # List printers -``` - -**Uninstall:** -```batch -uninstall_service.bat # Complete removal -``` - -**Log Files:** -``` -C:\Program Files\QualityRecticel\PrintService\print_service.log -``` - -## Advanced Configuration - -**Custom Port:** -Edit `print_service.ps1` and change: -```powershell -param([int]$Port = 8765) # Change to desired port -``` - -**Service Account:** -```batch -# Run service as specific user (optional) -sc config QualityRecticelPrintService obj="domain\username" password="password" -``` - -## Performance Notes - -- **Startup**: ~2 seconds (vs 10+ seconds for Python) -- **Memory**: ~15MB RAM (vs 50MB+ for Python/Flask) -- **CPU**: Minimal background usage -- **Dependencies**: Zero external packages - -## Need Help? - -1. **Check logs first** - PowerShell errors are detailed -2. **Test step by step** - service → health → printers → printing -3. **Verify PowerShell policy** - execution policy must allow scripts -4. **Contact IT support** with specific error messages - -## Success Checklist - -- [ ] Installer ran without errors -- [ ] Service shows "RUNNING" status: `sc query QualityRecticelPrintService` -- [ ] Health endpoint responds: http://localhost:8765/health -- [ ] Printers endpoint lists devices: http://localhost:8765/printers -- [ ] Chrome extension loaded and enabled -- [ ] Web app can print labels silently - ---- -✅ **Success**: Native service running + Extension loaded + Silent printing works -📞 **Support**: Contact Quality Recticel IT with log details if issues persist - -**Advantages over Python version:** -🚀 No external dependencies | ⚡ Faster startup | 🛡️ Better security | 📦 Smaller footprint \ No newline at end of file diff --git a/windows_print_service/chrome_extension/content.js b/windows_print_service/chrome_extension/content.js index 137a390..103d580 100644 --- a/windows_print_service/chrome_extension/content.js +++ b/windows_print_service/chrome_extension/content.js @@ -3,17 +3,25 @@ * Injects print service functionality into web pages */ -// Only inject on Quality Label Printing domains or localhost -const allowedDomains = [ - 'localhost', - '127.0.0.1' -]; -const currentDomain = window.location.hostname; -if (!allowedDomains.includes(currentDomain)) { - console.log('Quality Label Printing Service: Not injecting on', currentDomain); - // return; // Commented out for development - remove in production +// Inject extension ID into the page as a hidden DOM element for detection +function injectExtensionId() { + try { + const existing = document.getElementById('chrome-extension-id'); + if (existing) return; // Already injected + const el = document.createElement('div'); + el.id = 'chrome-extension-id'; + el.setAttribute('data-extension-id', chrome.runtime.id); + el.style.display = 'none'; + document.documentElement.appendChild(el); + // Optionally, also set a global variable + window.CHROME_EXTENSION_ID = chrome.runtime.id; + console.log('Injected extension ID:', chrome.runtime.id); + } catch (e) { + console.warn('Failed to inject extension ID:', e); + } } +injectExtensionId(); console.log('Quality Label Printing Service: Content script loaded'); diff --git a/windows_print_service/chrome_extension/icons/README.md b/windows_print_service/chrome_extension/icons/README.md new file mode 100644 index 0000000..5a5a982 --- /dev/null +++ b/windows_print_service/chrome_extension/icons/README.md @@ -0,0 +1,7 @@ +# Placeholder for icon files - in production, add actual PNG icons: +# - icon16.png (16x16 pixels) +# - icon48.png (48x48 pixels) +# - icon128.png (128x128 pixels) + +# For now, create simple text-based icons using SVG converted to PNG +# These should be replaced with proper icons later \ No newline at end of file diff --git a/windows_print_service/chrome_extension/icons/create_icons.py b/windows_print_service/chrome_extension/icons/create_icons.py new file mode 100644 index 0000000..a051067 --- /dev/null +++ b/windows_print_service/chrome_extension/icons/create_icons.py @@ -0,0 +1,27 @@ +# Simple text-based icons for the Chrome extension +# These are placeholder files - replace with actual PNG icons for production + +# Create simple SVG icons that can be converted to PNG +ICON_16_SVG = ''' + + + 🖨 + +''' + +ICON_48_SVG = ''' + + + 🖨️ + +''' + +ICON_128_SVG = ''' + + + 🖨️ + +''' + +# For now, create simple text placeholders +# In production, convert these SVGs to PNG files \ No newline at end of file diff --git a/windows_print_service/chrome_extension/icons/generate_icons.py b/windows_print_service/chrome_extension/icons/generate_icons.py new file mode 100644 index 0000000..ad8ef3b --- /dev/null +++ b/windows_print_service/chrome_extension/icons/generate_icons.py @@ -0,0 +1,122 @@ +#!/usr/bin/env python3 +""" +Generate PNG icons for Chrome extension +Creates simple colored squares with printer icons +""" + +try: + from PIL import Image, ImageDraw, ImageFont + PIL_AVAILABLE = True +except ImportError: + PIL_AVAILABLE = False + +import os + +def create_simple_icon(size, filename): + """Create a simple colored square icon""" + if PIL_AVAILABLE: + # Create image with PIL + img = Image.new('RGBA', (size, size), (0, 123, 255, 255)) # Blue background + draw = ImageDraw.Draw(img) + + # Add a white border + border_width = max(1, size // 16) + draw.rectangle([0, 0, size-1, size-1], outline=(255, 255, 255, 255), width=border_width) + + # Add text (P for Print) + try: + font_size = size // 2 + font = ImageFont.load_default() + text = "P" + bbox = draw.textbbox((0, 0), text, font=font) + text_width = bbox[2] - bbox[0] + text_height = bbox[3] - bbox[1] + x = (size - text_width) // 2 + y = (size - text_height) // 2 + draw.text((x, y), text, fill=(255, 255, 255, 255), font=font) + except: + pass + + img.save(filename, 'PNG') + print(f"Created {filename} ({size}x{size})") + else: + # Create a minimal PNG file without PIL + create_minimal_png(size, filename) + +def create_minimal_png(size, filename): + """Create a minimal PNG file without PIL""" + # This creates a very basic PNG file + # Blue square with minimal PNG structure + + import struct + import zlib + + # PNG signature + png_signature = b'\x89PNG\r\n\x1a\n' + + # IHDR chunk + width = height = size + bit_depth = 8 + color_type = 2 # RGB + compression = 0 + filter_method = 0 + interlace = 0 + + ihdr_data = struct.pack('>IIBBBBB', width, height, bit_depth, color_type, compression, filter_method, interlace) + ihdr_crc = zlib.crc32(b'IHDR' + ihdr_data) & 0xffffffff + ihdr_chunk = struct.pack('>I', len(ihdr_data)) + b'IHDR' + ihdr_data + struct.pack('>I', ihdr_crc) + + # IDAT chunk (blue pixels) + pixels = [] + for y in range(height): + row = [0] # Filter byte + for x in range(width): + # Blue color RGB(0, 123, 255) + row.extend([0, 123, 255]) + pixels.extend(row) + + pixel_data = bytes(pixels) + compressed_data = zlib.compress(pixel_data) + idat_crc = zlib.crc32(b'IDAT' + compressed_data) & 0xffffffff + idat_chunk = struct.pack('>I', len(compressed_data)) + b'IDAT' + compressed_data + struct.pack('>I', idat_crc) + + # IEND chunk + iend_crc = zlib.crc32(b'IEND') & 0xffffffff + iend_chunk = struct.pack('>I', 0) + b'IEND' + struct.pack('>I', iend_crc) + + # Write PNG file + with open(filename, 'wb') as f: + f.write(png_signature) + f.write(ihdr_chunk) + f.write(idat_chunk) + f.write(iend_chunk) + + print(f"Created minimal {filename} ({size}x{size})") + +def main(): + # Create icons directory if it doesn't exist + icons_dir = "/home/ske087/quality_recticel/windows_print_service/chrome_extension/icons" + + print("Creating Chrome extension icons...") + print(f"PIL available: {PIL_AVAILABLE}") + + # Create required icon sizes + sizes = [16, 32, 48, 128] + + for size in sizes: + filename = os.path.join(icons_dir, f"icon{size}.png") + create_simple_icon(size, filename) + + print("✅ All icons created successfully!") + print("\nIcons created:") + for size in sizes: + filename = f"icon{size}.png" + filepath = os.path.join(icons_dir, filename) + if os.path.exists(filepath): + file_size = os.path.getsize(filepath) + print(f" ✓ {filename} ({size}x{size}px, {file_size} bytes)") + else: + print(f" ✗ {filename} - FAILED") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/windows_print_service/chrome_extension/icons/icon128.png b/windows_print_service/chrome_extension/icons/icon128.png new file mode 100644 index 0000000000000000000000000000000000000000..453260a1eea51e005a49a85b9133f1b99c977935 GIT binary patch literal 545 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7xzrV0_`};uumf=j~nnRpNm%?GN9_ z{7>;sGn9VYWW6K$0`Cph?rMXVQa)*$#SZFL2rhPYnUK8W=o9@){vUh%rDkx>|6r7U zOD21*Y2w_saufD)F@!VhU^*bmu#TaCwPAPNtH#GSL(iPpzbWp|-@1H;7{&*vDzxb* zH1|*TjsLHHf1de$e)_dDcPwUC{wjNR_~^UP@P6J0&)3gvU$3|R{r#DhwRWE$JiX^@ zUBYta%<}cd<@xX9Ho7yOIkULhSbqPTxQ*=9EO+*peSY`xOyQZD--e%`oPFn7y@B!1 zPhN%B96+D<@jkfLxj#$$21C(GP$1CJi=qtgBC^-sRo>)}5!Cw`ze_3=S9v^K0*rbF MPgg&ebxsLQ02zhMumAu6 literal 0 HcmV?d00001 diff --git a/windows_print_service/chrome_extension/icons/icon128.txt b/windows_print_service/chrome_extension/icons/icon128.txt new file mode 100644 index 0000000..6b301ee --- /dev/null +++ b/windows_print_service/chrome_extension/icons/icon128.txt @@ -0,0 +1,4 @@ +# Placeholder for 128x128 icon +# This is a text file placeholder +# Replace with actual icon128.png file +🖨️ \ No newline at end of file diff --git a/windows_print_service/chrome_extension/icons/icon16.png b/windows_print_service/chrome_extension/icons/icon16.png new file mode 100644 index 0000000000000000000000000000000000000000..7bb53231147df3e1918c348c0080735a26250473 GIT binary patch literal 207 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`(>z@qLn`JZ|M~y_e?1!)12;1e zB=c6z`oG=4z#uTmn)&bsg9g^=N48h7wFUc2Nc=N4FxX&ZXdsf8oM7>H3JY`H-_(SH z8Qh7lyNnIaa3>`g%vKa);n#<_(QE~Dqw?DsuE@bd@^>bP0l+XkK DA`?af literal 0 HcmV?d00001 diff --git a/windows_print_service/chrome_extension/icons/icon16.txt b/windows_print_service/chrome_extension/icons/icon16.txt new file mode 100644 index 0000000..8b2cace --- /dev/null +++ b/windows_print_service/chrome_extension/icons/icon16.txt @@ -0,0 +1,4 @@ +# Placeholder for 16x16 icon +# This is a text file placeholder +# Replace with actual icon16.png file +🖨️ \ No newline at end of file diff --git a/windows_print_service/chrome_extension/icons/icon32.png b/windows_print_service/chrome_extension/icons/icon32.png new file mode 100644 index 0000000000000000000000000000000000000000..6f898d481226f9d4934943984ffa12963213957e GIT binary patch literal 235 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=ffJO`a}}Ar*6y|NQ^|zrKx;#fLf5 zk{JlNdk#i8$9MnV?(l+ls)f)%5SFyDP`%6gtGd3{TU}R_@ zl9!xd@pnoe-;b};8xJPxwXD@Xcfdfuqw!#g)yroAs$6>eQxXD1RQ&ZrWge_KbD-d% zWo6aLJ|?p?wl?OIJUl%;JUsIC1_n!V7HnpoaG60h;lSpH7fKr(PBU0HM8T|n-0@zP anIXhhadZ8tsvkhtGkCiCxvXWaiZ#UzQ6J9BCEaBI Z!{{_!@iGImYc0^f44$rjF6*2UngFp7d0zkk literal 0 HcmV?d00001 diff --git a/windows_print_service/chrome_extension/icons/icon48.txt b/windows_print_service/chrome_extension/icons/icon48.txt new file mode 100644 index 0000000..521d5ba --- /dev/null +++ b/windows_print_service/chrome_extension/icons/icon48.txt @@ -0,0 +1,4 @@ +# Placeholder for 48x48 icon +# This is a text file placeholder +# Replace with actual icon48.png file +🖨️ \ No newline at end of file diff --git a/windows_print_service/print_service.py b/windows_print_service/print_service.py deleted file mode 100644 index deb372a..0000000 --- a/windows_print_service/print_service.py +++ /dev/null @@ -1,374 +0,0 @@ -""" -Quality Recticel Windows Print Service -===================================== - -A local Windows service that provides a REST API for silent printing -through Chrome extension integration. - -Features: -- Local HTTP API server (localhost:8765) -- Chrome extension native messaging -- Silent PDF printing -- Windows service management -- Security and error handling - -Installation: -1. Run install_service.bat as Administrator -2. Install Chrome extension -3. Configure web application to use localhost:8765 - -Author: Quality Recticel Development Team -Version: 1.0.0 -""" - -import sys -import os -import json -import logging -import threading -from datetime import datetime -from pathlib import Path - -# Add current directory to path for imports -sys.path.append(os.path.dirname(os.path.abspath(__file__))) - -from flask import Flask, request, jsonify, send_file -from flask_cors import CORS -import requests -import subprocess -import tempfile -import uuid -import time - -# Configure logging -logging.basicConfig( - level=logging.INFO, - format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', - handlers=[ - logging.FileHandler('print_service.log'), - logging.StreamHandler() - ] -) -logger = logging.getLogger(__name__) - -class WindowsPrintService: - """Main Windows Print Service class""" - - def __init__(self, port=8765): - self.port = port - self.app = Flask(__name__) - CORS(self.app) # Enable CORS for web page communication - self.setup_routes() - self.chrome_extension_id = None - self.service_status = "starting" - - def setup_routes(self): - """Set up Flask routes for the API""" - - @self.app.route('/health', methods=['GET']) - def health_check(): - """Health check endpoint""" - return jsonify({ - 'status': 'healthy', - 'service': 'Quality Recticel Print Service', - 'version': '1.0.0', - 'timestamp': datetime.now().isoformat(), - 'chrome_extension_connected': self.is_chrome_extension_available() - }) - - @self.app.route('/print/pdf', methods=['POST']) - def print_pdf(): - """Print PDF endpoint""" - try: - data = request.get_json() - - # Validate required fields - required_fields = ['pdf_url', 'printer_name'] - for field in required_fields: - if field not in data: - return jsonify({ - 'error': f'Missing required field: {field}', - 'success': False - }), 400 - - # Execute print job - result = self.execute_print_job(data) - - if result['success']: - return jsonify(result), 200 - else: - return jsonify(result), 500 - - except Exception as e: - logger.error(f"Print PDF error: {e}") - return jsonify({ - 'error': str(e), - 'success': False - }), 500 - - @self.app.route('/print/silent', methods=['POST']) - def silent_print(): - """Silent print endpoint using Chrome extension""" - try: - data = request.get_json() - - # Validate required fields - if 'pdf_data' not in data and 'pdf_url' not in data: - return jsonify({ - 'error': 'Either pdf_data or pdf_url is required', - 'success': False - }), 400 - - # Send to Chrome extension for silent printing - result = self.send_to_chrome_extension(data) - - if result['success']: - return jsonify(result), 200 - else: - return jsonify(result), 500 - - except Exception as e: - logger.error(f"Silent print error: {e}") - return jsonify({ - 'error': str(e), - 'success': False - }), 500 - - @self.app.route('/printers', methods=['GET']) - def get_printers(): - """Get available printers""" - try: - printers = self.get_available_printers() - return jsonify({ - 'printers': printers, - 'success': True - }) - except Exception as e: - logger.error(f"Get printers error: {e}") - return jsonify({ - 'error': str(e), - 'success': False - }), 500 - - @self.app.route('/extension/status', methods=['GET']) - def extension_status(): - """Check Chrome extension status""" - return jsonify({ - 'extension_available': self.is_chrome_extension_available(), - 'success': True - }) - - def execute_print_job(self, print_data): - """Execute a print job""" - try: - pdf_url = print_data.get('pdf_url') - printer_name = print_data.get('printer_name', 'default') - copies = print_data.get('copies', 1) - - logger.info(f"Executing print job: {pdf_url} -> {printer_name}") - - # Download PDF if URL provided - if pdf_url: - pdf_content = self.download_pdf(pdf_url) - else: - pdf_content = print_data.get('pdf_data') - - if not pdf_content: - return { - 'success': False, - 'error': 'No PDF content available' - } - - # Save PDF to temporary file - temp_pdf = self.save_temp_pdf(pdf_content) - - # Print using system command - print_result = self.print_pdf_file(temp_pdf, printer_name, copies) - - # Cleanup - os.unlink(temp_pdf) - - return { - 'success': print_result, - 'message': 'Print job completed' if print_result else 'Print job failed', - 'job_id': str(uuid.uuid4()) - } - - except Exception as e: - logger.error(f"Execute print job error: {e}") - return { - 'success': False, - 'error': str(e) - } - - def send_to_chrome_extension(self, print_data): - """Send print command to Chrome extension""" - try: - # Prepare message for Chrome extension - message = { - 'action': 'silent_print', - 'data': print_data, - 'timestamp': datetime.now().isoformat(), - 'job_id': str(uuid.uuid4()) - } - - # Try to communicate with Chrome extension via native messaging - result = self.send_native_message(message) - - if result: - return { - 'success': True, - 'message': 'Print command sent to Chrome extension', - 'job_id': message['job_id'] - } - else: - # Fallback to direct printing - logger.warning("Chrome extension not available, falling back to direct printing") - return self.execute_print_job(print_data) - - except Exception as e: - logger.error(f"Send to Chrome extension error: {e}") - return { - 'success': False, - 'error': str(e) - } - - def send_native_message(self, message): - """Send native message to Chrome extension""" - try: - # This would be implemented based on Chrome's native messaging protocol - # For now, we'll simulate the communication - - # In a real implementation, this would: - # 1. Find Chrome extension by ID - # 2. Send message via stdin/stdout pipe - # 3. Wait for response - - logger.info(f"Sending native message to Chrome extension: {message}") - - # Simulate successful communication - return True - - except Exception as e: - logger.error(f"Native messaging error: {e}") - return False - - def download_pdf(self, url): - """Download PDF from URL""" - try: - response = requests.get(url, timeout=30) - response.raise_for_status() - return response.content - except Exception as e: - logger.error(f"PDF download error: {e}") - raise - - def save_temp_pdf(self, pdf_content): - """Save PDF content to temporary file""" - temp_file = tempfile.mktemp(suffix='.pdf') - with open(temp_file, 'wb') as f: - if isinstance(pdf_content, str): - # Base64 encoded content - import base64 - pdf_content = base64.b64decode(pdf_content) - f.write(pdf_content) - return temp_file - - def print_pdf_file(self, pdf_path, printer_name, copies=1): - """Print PDF file using system command""" - try: - # Windows printing command - if printer_name == 'default': - cmd = f'powershell -Command "Start-Process -FilePath \\"{pdf_path}\\" -ArgumentList \\"/p\\" -Wait"' - else: - cmd = f'powershell -Command "Start-Process -FilePath \\"{pdf_path}\\" -ArgumentList \\"/p /h /{printer_name}\\" -Wait"' - - logger.info(f"Executing print command: {cmd}") - - result = subprocess.run(cmd, shell=True, capture_output=True, text=True) - - if result.returncode == 0: - logger.info("Print command executed successfully") - return True - else: - logger.error(f"Print command failed: {result.stderr}") - return False - - except Exception as e: - logger.error(f"Print PDF file error: {e}") - return False - - def get_available_printers(self): - """Get list of available printers""" - try: - # Windows command to get printers - cmd = 'powershell -Command "Get-Printer | Select-Object Name, DriverName, PortName | ConvertTo-Json"' - - result = subprocess.run(cmd, shell=True, capture_output=True, text=True) - - if result.returncode == 0: - printers_data = json.loads(result.stdout) - - # Ensure it's a list - if isinstance(printers_data, dict): - printers_data = [printers_data] - - printers = [] - for printer in printers_data: - printers.append({ - 'name': printer.get('Name', ''), - 'driver': printer.get('DriverName', ''), - 'port': printer.get('PortName', ''), - 'is_default': False # Could be enhanced to detect default printer - }) - - return printers - else: - logger.error(f"Failed to get printers: {result.stderr}") - return [] - - except Exception as e: - logger.error(f"Get available printers error: {e}") - return [] - - def is_chrome_extension_available(self): - """Check if Chrome extension is available""" - # This would check for Chrome extension via native messaging - # For now, we'll return a simulated status - return True - - def run_service(self): - """Run the Flask service""" - try: - self.service_status = "running" - logger.info(f"Starting Quality Recticel Print Service on port {self.port}") - - self.app.run( - host='localhost', - port=self.port, - debug=False, - threaded=True - ) - - except Exception as e: - logger.error(f"Service run error: {e}") - self.service_status = "error" - finally: - self.service_status = "stopped" - -def main(): - """Main entry point""" - print("Quality Recticel Windows Print Service") - print("=====================================") - - service = WindowsPrintService() - - try: - service.run_service() - except KeyboardInterrupt: - logger.info("Service stopped by user") - except Exception as e: - logger.error(f"Service error: {e}") - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/windows_print_service/service_manager.py b/windows_print_service/service_manager.py deleted file mode 100644 index ecc3bf8..0000000 --- a/windows_print_service/service_manager.py +++ /dev/null @@ -1,143 +0,0 @@ -""" -Windows Service Installation and Management -========================================== - -This module handles Windows service installation, configuration, and management -for the Quality Recticel Print Service. -""" - -import os -import sys -import time -import win32serviceutil -import win32service -import win32event -import servicemanager -import socket -from pathlib import Path - -class QualityRecticelPrintService(win32serviceutil.ServiceFramework): - """Windows Service wrapper for the print service""" - - _svc_name_ = "QualityRecticelPrintService" - _svc_display_name_ = "Quality Recticel Print Service" - _svc_description_ = "Local API service for silent PDF printing via Chrome extension" - - def __init__(self, args): - win32serviceutil.ServiceFramework.__init__(self, args) - self.hWaitStop = win32event.CreateEvent(None, 0, 0, None) - self.is_alive = True - - def SvcStop(self): - """Stop the service""" - self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING) - win32event.SetEvent(self.hWaitStop) - self.is_alive = False - servicemanager.LogMsg( - servicemanager.EVENTLOG_INFORMATION_TYPE, - servicemanager.PYS_SERVICE_STOPPED, - (self._svc_name_, '') - ) - - def SvcDoRun(self): - """Run the service""" - servicemanager.LogMsg( - servicemanager.EVENTLOG_INFORMATION_TYPE, - servicemanager.PYS_SERVICE_STARTED, - (self._svc_name_, '') - ) - - # Import and run the print service - try: - from print_service import WindowsPrintService - - service = WindowsPrintService(port=8765) - - # Run service in a separate thread - import threading - service_thread = threading.Thread(target=service.run_service) - service_thread.daemon = True - service_thread.start() - - # Wait for stop event - win32event.WaitForSingleObject(self.hWaitStop, win32event.INFINITE) - - except Exception as e: - servicemanager.LogErrorMsg(f"Service error: {e}") - -def install_service(): - """Install the Windows service""" - try: - # Install the service with automatic startup - win32serviceutil.InstallService( - QualityRecticelPrintService._svc_reg_class_, - QualityRecticelPrintService._svc_name_, - QualityRecticelPrintService._svc_display_name_, - description=QualityRecticelPrintService._svc_description_, - startType=win32service.SERVICE_AUTO_START # Auto-start on system boot - ) - - print(f"✅ Service '{QualityRecticelPrintService._svc_display_name_}' installed successfully") - print(f"🔄 Service configured for AUTOMATIC startup on system restart") - - # Start the service - win32serviceutil.StartService(QualityRecticelPrintService._svc_name_) - print(f"✅ Service started successfully") - - return True - - except Exception as e: - print(f"❌ Service installation failed: {e}") - return False - -def uninstall_service(): - """Uninstall the Windows service""" - try: - # Stop the service first - try: - win32serviceutil.StopService(QualityRecticelPrintService._svc_name_) - print(f"✅ Service stopped") - except: - pass # Service might not be running - - # Remove the service - win32serviceutil.RemoveService(QualityRecticelPrintService._svc_name_) - print(f"✅ Service '{QualityRecticelPrintService._svc_display_name_}' uninstalled successfully") - - return True - - except Exception as e: - print(f"❌ Service uninstallation failed: {e}") - return False - -def service_status(): - """Get service status""" - try: - status = win32serviceutil.QueryServiceStatus(QualityRecticelPrintService._svc_name_) - - status_names = { - win32service.SERVICE_STOPPED: "Stopped", - win32service.SERVICE_START_PENDING: "Start Pending", - win32service.SERVICE_STOP_PENDING: "Stop Pending", - win32service.SERVICE_RUNNING: "Running", - win32service.SERVICE_CONTINUE_PENDING: "Continue Pending", - win32service.SERVICE_PAUSE_PENDING: "Pause Pending", - win32service.SERVICE_PAUSED: "Paused" - } - - current_status = status_names.get(status[1], "Unknown") - print(f"Service Status: {current_status}") - - return status[1] - - except Exception as e: - print(f"❌ Failed to get service status: {e}") - return None - -if __name__ == '__main__': - if len(sys.argv) == 1: - servicemanager.Initialize() - servicemanager.PrepareToHostSingle(QualityRecticelPrintService) - servicemanager.StartServiceCtrlDispatcher() - else: - win32serviceutil.HandleCommandLine(QualityRecticelPrintService) \ No newline at end of file