updated to silent print
This commit is contained in:
74
py_app/PRINT_SERVICE_SETUP.md
Normal file
74
py_app/PRINT_SERVICE_SETUP.md
Normal file
@@ -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
|
||||
```
|
||||
125
py_app/analyze_pdf_dimensions.py
Normal file
125
py_app/analyze_pdf_dimensions.py
Normal file
@@ -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")
|
||||
Binary file not shown.
16
py_app/app/print_config.py
Normal file
16
py_app/app/print_config.py
Normal file
@@ -0,0 +1,16 @@
|
||||
# Windows Print Service Configuration
|
||||
# Configure the Windows Print Service connection for cross-platform printing
|
||||
|
||||
# Default Windows Print Service URL
|
||||
# Change this to the IP address of your Windows machine if running on different systems
|
||||
# Examples:
|
||||
# Same machine: http://localhost:8765
|
||||
# Different machine: http://192.168.1.100:8765
|
||||
# Domain name: http://printserver.company.local:8765
|
||||
WINDOWS_PRINT_SERVICE_URL = "http://192.168.1.XXX:8765" # ← REPLACE XXX WITH YOUR WINDOWS IP
|
||||
|
||||
# Print service connection timeout (seconds)
|
||||
PRINT_SERVICE_TIMEOUT = 30
|
||||
|
||||
# Enable print service connection (set to False to always use PDF fallback)
|
||||
PRINT_SERVICE_ENABLED = True
|
||||
@@ -1616,145 +1616,13 @@ def import_locations_csv():
|
||||
from app.warehouse import import_locations_csv_handler
|
||||
return import_locations_csv_handler()
|
||||
|
||||
@bp.route('/print_labels_silent/<int:order_id>', 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
|
||||
# NOTE for frontend/extension developers:
|
||||
# To print labels, call the Chrome extension and pass the PDF URL:
|
||||
# /generate_labels_pdf/<order_id>
|
||||
# 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
|
||||
# }
|
||||
@@ -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,10 +860,17 @@ 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) return;
|
||||
|
||||
if (printButton) {
|
||||
printButton.addEventListener('click', async function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
@@ -866,7 +884,6 @@ function addPDFGenerationHandler() {
|
||||
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;
|
||||
@@ -879,47 +896,65 @@ function addPDFGenerationHandler() {
|
||||
printButton.disabled = true;
|
||||
|
||||
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...');
|
||||
// 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 {
|
||||
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);
|
||||
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 {
|
||||
console.log('📄 Service not available, using PDF download mode');
|
||||
alert('❌ Failed to print via extension. PDF will be downloaded.');
|
||||
downloadPDFLabels(orderId, prodOrder, quantity);
|
||||
}
|
||||
|
||||
// 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}`);
|
||||
}
|
||||
|
||||
);
|
||||
} 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}\n\nPlease check:\n• Windows Print Service is running\n• Chrome extension is installed\n• Network connectivity\n• PDF generation permissions`);
|
||||
alert('❌ Print operation failed: ' + error.message);
|
||||
} finally {
|
||||
// Reset button state
|
||||
printButton.innerHTML = originalText;
|
||||
printButton.style.background = originalColor;
|
||||
printButton.disabled = false;
|
||||
|
||||
// Recheck service availability for next operation
|
||||
setTimeout(checkPrintServiceAvailability, 2000);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -6,3 +6,4 @@ flask-sqlalchemy
|
||||
pyodbc
|
||||
mariadb
|
||||
reportlab
|
||||
requests
|
||||
161
py_app/test_print_connection.py
Normal file
161
py_app/test_print_connection.py
Normal file
@@ -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()
|
||||
18
windows_print_service/CLEANUP_COMMANDS.sh
Normal file
18
windows_print_service/CLEANUP_COMMANDS.sh
Normal file
@@ -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."
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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*
|
||||
@@ -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 <process_id> /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
|
||||
@@ -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');
|
||||
|
||||
|
||||
7
windows_print_service/chrome_extension/icons/README.md
Normal file
7
windows_print_service/chrome_extension/icons/README.md
Normal file
@@ -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
|
||||
27
windows_print_service/chrome_extension/icons/create_icons.py
Normal file
27
windows_print_service/chrome_extension/icons/create_icons.py
Normal file
@@ -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 = '''
|
||||
<svg width="16" height="16" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="16" height="16" fill="#007bff"/>
|
||||
<text x="8" y="12" font-family="Arial" font-size="10" fill="white" text-anchor="middle">🖨</text>
|
||||
</svg>
|
||||
'''
|
||||
|
||||
ICON_48_SVG = '''
|
||||
<svg width="48" height="48" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="48" height="48" fill="#007bff" rx="8"/>
|
||||
<text x="24" y="32" font-family="Arial" font-size="24" fill="white" text-anchor="middle">🖨️</text>
|
||||
</svg>
|
||||
'''
|
||||
|
||||
ICON_128_SVG = '''
|
||||
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="128" height="128" fill="#007bff" rx="16"/>
|
||||
<text x="64" y="84" font-family="Arial" font-size="48" fill="white" text-anchor="middle">🖨️</text>
|
||||
</svg>
|
||||
'''
|
||||
|
||||
# For now, create simple text placeholders
|
||||
# In production, convert these SVGs to PNG files
|
||||
122
windows_print_service/chrome_extension/icons/generate_icons.py
Normal file
122
windows_print_service/chrome_extension/icons/generate_icons.py
Normal file
@@ -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()
|
||||
BIN
windows_print_service/chrome_extension/icons/icon128.png
Normal file
BIN
windows_print_service/chrome_extension/icons/icon128.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 545 B |
4
windows_print_service/chrome_extension/icons/icon128.txt
Normal file
4
windows_print_service/chrome_extension/icons/icon128.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
# Placeholder for 128x128 icon
|
||||
# This is a text file placeholder
|
||||
# Replace with actual icon128.png file
|
||||
🖨️
|
||||
BIN
windows_print_service/chrome_extension/icons/icon16.png
Normal file
BIN
windows_print_service/chrome_extension/icons/icon16.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 207 B |
4
windows_print_service/chrome_extension/icons/icon16.txt
Normal file
4
windows_print_service/chrome_extension/icons/icon16.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
# Placeholder for 16x16 icon
|
||||
# This is a text file placeholder
|
||||
# Replace with actual icon16.png file
|
||||
🖨️
|
||||
BIN
windows_print_service/chrome_extension/icons/icon32.png
Normal file
BIN
windows_print_service/chrome_extension/icons/icon32.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 235 B |
BIN
windows_print_service/chrome_extension/icons/icon48.png
Normal file
BIN
windows_print_service/chrome_extension/icons/icon48.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 285 B |
4
windows_print_service/chrome_extension/icons/icon48.txt
Normal file
4
windows_print_service/chrome_extension/icons/icon48.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
# Placeholder for 48x48 icon
|
||||
# This is a text file placeholder
|
||||
# Replace with actual icon48.png file
|
||||
🖨️
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
Reference in New Issue
Block a user