## Virtual printer for capturing and saving print jobs on SAP label printers
## It also creates a bat file for copy the captures onto a jetdirect printserver
## 2007.11.20: created janos.juhasz@velux.com
## Python can be downloaded from http:\\www.activestate.com
## 1. Registering pythonservice: c:\Python24\Lib\site-packages\win32\pythonservice.exe \register
## 2. Install virtual printer: VirtualPrinterService.py install
## 3. Set it to be auto: VirtualPrinterService.py --startup auto update
## 4. Start service: VirtualPrinterService.py start
## Debug: VirtualPrinterService.py debug
## 2007.11.21   class PrinterServerThread(Thread):
##                  def run(self):
##                      while 1: ## restart capture at exception
## 2007.11.22 print -> log
## 2007.11.24 debug

from threading import Thread, Timer
from Queue import Queue
import os, time, socket, sys
import win32serviceutil, win32service, win32event

## Settings
## The virtual printer will listen on these IP addresses
## I binded these IPs to the same NIC
servers = {'front':'10.36.24.51',
           'gable':'10.36.24.52',
           'top':  '10.36.24.53',
           }

printers = {'front':{'ALU':'10.36.40.22', 'W-St':'10.36.24.40'},
            'gable':{'ALU':'10.36.40.21', 'W-St':'10.36.24.41'},
            'top':  {'ALU':'10.36.40.20', 'W-St':'10.36.24.42'},
            }

destfolder = r'd:\kozos\programs\SAP_PrintCapture\\'

#################################################
class CaptureManager:
    def __init__(self):
        self.maxtime = 10
        self.timer = None
        self.serverthreads = {}
        self.CleanOldFiles()

    def RemoveFiles(self, path, days=30):
        ## Remove files from folder older then days
        now = time.time()
        for f in os.listdir(path):
            filepath = os.path.join(path, f)
            if os.stat(filepath).st_mtime < now - days * 86400:
                try:
                    os.remove(filepath)
                except:
                    pass
        
    def CleanOldFiles(self):
        ## To keep the folders managable, remove the old bat files and captures
        self.RemoveFiles(os.path.join(destfolder + 'files\\'))
        self.RemoveFiles(os.path.join(destfolder + 'bat_files\\'))
        ##reregister itself        
        Timer(1000, self.CleanOldFiles)

    def CleanQueue(self):
        ## Reinitialise the queues
        try:
            for type, serverthread in self.serverthreads.iteritems():
                while not serverthread.queue.empty():
                    serverthread.queue.get()
        except:
            pass

    def CheckWatchDog(self):
        ## Reset the watchdog timer, if received new data
        if self.timer:
            self.timer.cancel()
        self.timer = Timer(self.maxtime, self.CleanQueue)
        self.timer.start()

    def GetBlockCode(self, content):
        ## Read the Block Number from the gable or top capture
        try:
            content = content.split('<FNC1>10')[1]
            return content[:2]
        except:
            return 'no_Block'

    def GetStockCode(self, content):
        ## Read the StockCode from the gable or top capture
        try:
            content = content.split('<FNC1>240')[1]
            content = content.split('#G')[0]
            stockcode = content.replace(' ', '_')
            return stockcode
        except:
            return 'no_StockCode'

    def write_files(self):
        ## Write the files for backup
        CurrentCapture = {}
        department = None

        ## Get the first from the FiFo
        for type, item in self.serverthreads.iteritems():
            CurrentCapture[type] = item.queue.get()

        StockCode = self.GetStockCode(CurrentCapture['gable'])
        BlockNumber = self.GetBlockCode(CurrentCapture['gable'])
        ## The base of the filenames
        basename = '%s_%s' % (BlockNumber, StockCode)
        ## Open the batfile for writing
        batfile = open(os.path.join(destfolder, 'bat_files', '%s.%s'%(basename, 'bat')), 'w')

        ## Write the captures one by one
        for type, content in CurrentCapture.iteritems():
            ## whole filename of the capture
            filepath = os.path.join(destfolder, 'files', (r'%s.%s' % (basename, type)))
            ## filename used in the bat file
            relfilename = r'..\files\%s.%s' % (basename, type)
            ## Find the production department from the StockCode
            if StockCode[0] == 'E': department = 'ALU'
            if StockCode[0] == 'G': department = 'W-St'
            
            if department:
                ## save the capture
                open(filepath, 'wb').write(content)
                destaddr = printers[type][department]
                ## write the printing command into the bat file
                batfile.write('..\\prg\\nc %s 9100 -w 1 < %s\n' % (destaddr, relfilename))
        ## Close the bat file, when everithing written
        batfile.close()
            
#################################################
class PrinterServerThread(Thread):
    def __init__(self, address, port, type, manager):
        Thread.__init__(self)
        self.setDaemon(True) 
        self.address = address
        self.port = port
        self.type = type
        self.manager = manager
        self.queue = Queue()

    def HandleClient(self, soc):
        try:
            content = ''
            while 1:
                data = soc.recv(1024)
                if not data:
                    self.queue.put(content)
                    break
                content += data
        finally:
            soc.close()

    def run(self):
        ServerSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        ServerSocket.bind((self.address, self.port))
        ServerSocket.listen(1)
        while 1:
            soc, addr = ServerSocket.accept()
            self.manager.CheckWatchDog()
            self.HandleClient(soc)
            ## Check the sortest queue length
            minQueueLength = min([item.queue.qsize() for item in self.manager.serverthreads.values()])
            ## We can write only after each printerfinished the capture
            if minQueueLength >= 1:
                self.manager.write_files()


class CaptureService(win32serviceutil.ServiceFramework):
    ## The service itself
    _svc_name_ = "SAP_Virtual_Printer"
    _svc_display_name_ = "SAP_Virtual_Printer"
    _svc_description_ = "Captures the printouts and makes bat file to print them off-line"

    def __init__(self, args):
        win32serviceutil.ServiceFramework.__init__(self, args)
        self.hWaitStop = win32event.CreateEvent(None, 0, 0, None)

    def SvcStop(self):
        self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
        win32event.SetEvent(self.hWaitStop)

    def SvcDoRun(self):
        man = CaptureManager()
        for type in servers:
            addr = servers[type]
            port = servers[type]
            man.serverthreads[type] = PrinterServerThread(addr, 9100, type, man)
            man.serverthreads[type].start()
        win32event.WaitForSingleObject(self.hWaitStop, win32event.INFINITE)
     
if __name__ == '__main__':
    win32serviceutil.HandleCommandLine(CaptureService)
