wxDialogStatus.py
上传用户:lswyart
上传日期:2008-06-12
资源大小:3441k
文件大小:20k
源码类别:

杀毒

开发平台:

Visual C++

  1. #-----------------------------------------------------------------------------
  2. #Boa:Dialog:wxDialogStatus
  3. #-----------------------------------------------------------------------------
  4. # Name:        wxDialogStatus.py
  5. # Product:     ClamWin Free Antivirus
  6. #
  7. # Author:      alch [alch at users dot sourceforge dot net]
  8. #
  9. # Created:     2004/19/03
  10. # Copyright:   Copyright alch (c) 2004
  11. # Licence:     
  12. #   This program is free software; you can redistribute it and/or modify
  13. #   it under the terms of the GNU General Public License as published by
  14. #   the Free Software Foundation; either version 2 of the License, or
  15. #   (at your option) any later version.
  16. #   This program is distributed in the hope that it will be useful,
  17. #   but WITHOUT ANY WARRANTY; without even the implied warranty of
  18. #   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  19. #   GNU General Public License for more details.
  20. #   You should have received a copy of the GNU General Public License
  21. #   along with this program; if not, write to the Free Software
  22. #   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  23. from wxPython.wx import *
  24. from wxPython.lib.throbber import Throbber
  25. from threading import *
  26. from throb import throbImages
  27. import string
  28. import time
  29. import tempfile
  30. import Process
  31. import os, sys
  32. import re
  33. import MsgBox
  34. import Utils
  35. import wxDialogUtils
  36. import win32gui
  37. _WAIT_TIMEOUT = 5
  38. if sys.platform.startswith("win"):    
  39.     import win32event, win32api, winerror, win32con, win32gui
  40.     _KILL_SIGNAL = None    
  41.     _WAIT_NOWAIT = 0
  42.     _NEWLINE_LEN=2
  43. else:
  44.     import signal, os
  45.     _KILL_SIGNAL = signal.SIGKILL
  46.     _WAIT_NOWAIT = os.WNOHANG
  47.     _NEWLINE_LEN=1
  48.                     
  49. class StatusUpdateBuffer(Process.IOBuffer):            
  50.     def __init__(self,  caller, update, notify):
  51.         Process.IOBuffer.__init__(self)
  52.         self._caller = caller
  53.         self.update = update
  54.         self.notify = notify        
  55.         
  56.     def _doWrite(self, s):
  57.         # sometimes there is more than one line in the buffer
  58.         # so we need to call update method for every new line                                
  59.         lines = s.replace('r', 'n').splitlines(True)        
  60.         for line in lines:              
  61.             if sys.platform.startswith('win'):        
  62.                 # replace cygwin-like pathes with windows-like
  63.                 line = line.replace('/', '\')                
  64.             self.update(self._caller, line)             
  65.             
  66.         
  67.         
  68.         # do not call original implementation
  69.         # Process.IOBuffer._doWrite(self, s)
  70.         
  71.     def _doClose(self):
  72.         self.notify(self._caller)
  73.         Process.IOBuffer._doClose(self)
  74. # custom command events sent from worker thread when it finishes    
  75. # and when status message needs updating
  76. # the handler updates buttons and status text
  77. THREADFINISHED = wxNewEventType() 
  78. def EVT_THREADFINISHED( window, function ):     
  79.     window.Connect( -1, -1, THREADFINISHED, function ) 
  80.  
  81. class ThreadFinishedEvent(wxPyCommandEvent): 
  82.     eventType = THREADFINISHED 
  83.     def __init__(self, windowID): 
  84.         wxPyCommandEvent.__init__(self, self.eventType, windowID) 
  85.  
  86.     def Clone( self ): 
  87.         self.__class__( self.GetId() )
  88.  
  89. THREADUPDATESTATUS = wxNewEventType() 
  90. def EVT_THREADUPDATESTATUS( window, function ):     
  91.     window.Connect( -1, -1, THREADUPDATESTATUS, function ) 
  92.            
  93. class ThreadUpdateStatusEvent(wxPyCommandEvent): 
  94.     eventType = THREADUPDATESTATUS 
  95.     def __init__(self, windowID, text, append): 
  96.         self.text = text
  97.         self.append = append
  98.         wxPyCommandEvent.__init__(self, self.eventType, windowID) 
  99.  
  100.     def Clone( self ): 
  101.         self.__class__( self.GetId() )
  102.         
  103. def create(parent, cmd, logfile, priority, bitmap_mask, notify_params=None):
  104.     return wxDialogStatus(parent, cmd, logfile, priority, bitmap_mask, notify_params)                 
  105. [wxID_WXDIALOGSTATUS, wxID_WXDIALOGSTATUSBUTTONSAVE, 
  106.  wxID_WXDIALOGSTATUSBUTTONSTOP, wxID_WXDIALOGSTATUSSTATICBITMAP1, 
  107.  wxID_WXDIALOGSTATUSTEXTCTRLSTATUS, 
  108. ] = map(lambda _init_ctrls: wxNewId(), range(5))
  109. class wxDialogStatus(wxDialog):
  110.     def _init_ctrls(self, prnt):
  111.         # generated method, don't edit
  112.         wxDialog.__init__(self, id=wxID_WXDIALOGSTATUS, name='wxDialogStatus',
  113.               parent=prnt, pos=wxPoint(449, 269), size=wxSize(568, 392),
  114.               style=wxDEFAULT_DIALOG_STYLE, title='ClamWin Free Antivirus Status')
  115.         self.SetClientSize(wxSize(560, 365))
  116.         self.SetAutoLayout(false)
  117.         self.Center(wxBOTH)
  118.         self.SetToolTipString('')
  119.         EVT_CLOSE(self, self.OnWxDialogStatusClose)
  120.         EVT_INIT_DIALOG(self, self.OnInitDialog)
  121.         winstyle = wxTAB_TRAVERSAL | wxTE_RICH | wxTE_MULTILINE | wxTE_READONLY
  122.         # enable wxTE_AUTO_URL on XP only
  123.         # 98 produces some weird scrolling behaviour
  124.         if win32api.GetVersionEx()[0] >= 5 and self._bitmapMask == 'update': 
  125.             winstyle = winstyle | wxTE_AUTO_URL
  126.             
  127.         self.textCtrlStatus = wxTextCtrl(id=wxID_WXDIALOGSTATUSTEXTCTRLSTATUS,
  128.               name='textCtrlStatus', parent=self, pos=wxPoint(89, 11),
  129.               size=wxSize(455, 300),
  130.               style=winstyle, value='')
  131.               
  132.         self.staticBitmap1 = wxStaticBitmap(bitmap=wxNullBitmap,
  133.               id=wxID_WXDIALOGSTATUSSTATICBITMAP1, name='staticBitmap1',
  134.               parent=self, pos=wxPoint(16, 9), size=wxSize(56, 300),
  135.               style=wxTRANSPARENT_WINDOW)
  136.         self.buttonStop = wxButton(id=wxID_WXDIALOGSTATUSBUTTONSTOP,
  137.               label='&Stop', name='buttonStop', parent=self, pos=wxPoint(291,
  138.               328), size=wxSize(85, 24), style=0)
  139.         self.buttonStop.Enable(True)
  140.         self.buttonStop.SetDefault()
  141.         EVT_BUTTON(self.buttonStop, wxID_WXDIALOGSTATUSBUTTONSTOP,
  142.               self.OnButtonStop)
  143.         self.buttonSave = wxButton(id=wxID_WXDIALOGSTATUSBUTTONSAVE,
  144.               label='S&ave Report', name='buttonSave', parent=self,
  145.               pos=wxPoint(192, 328), size=wxSize(86, 24), style=0)
  146.         self.buttonSave.Enable(False)
  147.         EVT_BUTTON(self.buttonSave, wxID_WXDIALOGSTATUSBUTTONSAVE,
  148.               self.OnButtonSave)
  149.     def __init__(self, parent, cmd, logfile, priority='n', bitmapMask="", notify_params=None):
  150.         self._autoClose = False
  151.         self._closeRetCode = None
  152.         self._cancelled = False        
  153.         self._logfile = logfile
  154.         self._returnCode = -1
  155.         self.terminating = False       
  156.         self._out = None
  157.         self._proc = None         
  158.         self._notify_params = notify_params
  159.         self._bitmapMask = bitmapMask
  160.         self._previousStart = 0
  161.         
  162.         self._init_ctrls(parent)
  163.                 
  164.         
  165.         # bind thread notification events
  166.         EVT_THREADFINISHED(self, self.OnThreadFinished)        
  167.         EVT_THREADUPDATESTATUS(self, self.OnThreadUpdateStatus)                
  168.         # add url click handler        
  169.         EVT_TEXT_URL(self, wxID_WXDIALOGSTATUSTEXTCTRLSTATUS, self.OnClickURL)
  170.         
  171.         # initilaise our throbber (an awkward way to display animated images)
  172.         images = [throbImages.catalog[i].getBitmap()
  173.                   for i in throbImages.index
  174.                   if i.find(bitmapMask) != -1]                        
  175.         self.throbber = Throbber(self, -1, images, frameDelay=0.1,
  176.                   pos=self.staticBitmap1.GetPosition(), size=self.staticBitmap1.GetSize(),
  177.                   style=self.staticBitmap1.GetWindowStyleFlag(), useParentBackground = True, name='staticThrobber')
  178.     
  179.         
  180.         # set window icons
  181.         icons = wxIconBundle()
  182.         icons.AddIconFromFile('img/FrameIcon.ico', wxBITMAP_TYPE_ICO)
  183.         self.SetIcons(icons)        
  184.         # change colour of read-only controls (gray)
  185.         self.textCtrlStatus.SetBackgroundColour(wxSystemSettings_GetColour(wxSYS_COLOUR_BTNFACE))        
  186.         
  187.         # spawn and monitor our process
  188.         # clamav stopped writing start time of the scan to the log file        
  189.         #try:
  190.         #    file(logfile, 'wt').write('Scan Started %sn' % time.ctime(time.time()))
  191.         #except:
  192.         #    pass    
  193.         try:
  194.             self._SpawnProcess(cmd, priority)            
  195.         except Process.ProcessError, e:
  196.             event = ThreadUpdateStatusEvent(self.GetId(), str(e), False)                         
  197.             self.GetEventHandler().AddPendingEvent(event)                 
  198.             event = ThreadFinishedEvent(self.GetId()) 
  199.             self.GetEventHandler().AddPendingEvent(event)                             
  200.         
  201.     def SetAutoClose(self, autoClose, closeRetCode=None):
  202.         self._autoClose = autoClose
  203.         self._closeRetCode = closeRetCode
  204.     
  205.     def OnWxDialogStatusClose(self, event):          
  206.          self.terminating = True         
  207.          self._StopProcess()                     
  208.          event.Skip()
  209.     
  210.     def _IsProcessRunning(self, wait=False):
  211.         if self._proc is None:
  212.             return False
  213.         
  214.         if wait:
  215.             timeout = _WAIT_TIMEOUT
  216.         else:
  217.             timeout = _WAIT_NOWAIT
  218.         try:        
  219.             self._proc.wait(timeout)                  
  220.         except Exception, e:
  221.             if isinstance(e, Process.ProcessError):
  222.                 if e.errno == Process.ProcessProxy.WAIT_TIMEOUT:        
  223.                     return True     
  224.                 else:
  225.                     return False
  226.         return False
  227.     def _StopProcess(self):  
  228.         # check if process is still running
  229.         if self._IsProcessRunning():       
  230.             # still running - kill
  231.             # terminate process and use KILL_SIGNAL to terminate gracefully
  232.             # do not wait too long for the process to finish                
  233.             self._proc.kill(sig=_KILL_SIGNAL)
  234.             
  235.             #wait to finish
  236.             if self._IsProcessRunning(True):       
  237.                 # still running, huh
  238.                 # kill unconditionally
  239.                 try:
  240.                     self._proc.kill()
  241.                 except Process.ProcessError:
  242.                     pass                               
  243.                 
  244.                 # last resort if failed to kill the process
  245.                 if self._IsProcessRunning():       
  246.                     MsgBox.ErrorBox(self, 'Unable to stop runner thread, terminating')
  247.                     os._exit(0)      
  248.                     
  249.             self._proc.close()
  250.             self._out.close()                                                    
  251.                 
  252.     def OnButtonStop(self, event):      
  253.         if self._IsProcessRunning():           
  254.             self._cancelled = True 
  255.             self._StopProcess()            
  256.         else:
  257.             self.Close()                    
  258.     def OnButtonSave(self, event):
  259.         filename = "clamav_report_" + time.strftime("%d%m%y_%H%M%S")
  260.         if sys.platform.startswith("win"):
  261.             filename +=  ".txt"
  262.             mask = "Report files (*.txt)|*.txt|All files (*.*)|*.*"
  263.         else:            
  264.             mask = "All files (*)|*"
  265.         dlg = wxFileDialog(self, "Choose a file", ".", filename, mask, wxSAVE)
  266.         try:
  267.             if dlg.ShowModal() == wxID_OK:
  268.                 filename = dlg.GetPath()
  269.                 try:
  270.                     file(filename, "w").write(self.textCtrlStatus.GetLabel())
  271.                 except:
  272.                     dlg = wxMessageDialog(self, 'Could not save report to the file ' + 
  273.                                             filename + ". Please check that you have write "
  274.                                             "permissions to the folder and there is enough space on the disk.",
  275.                       'ClamWin Free Antivirus', wxOK | wxICON_ERROR)
  276.                     try:
  277.                         dlg.ShowModal()
  278.                     finally:
  279.                         dlg.Destroy()
  280.         finally:
  281.             dlg.Destroy()
  282.             
  283.             
  284.     def ThreadFinished(owner):    
  285.         if owner.terminating:            
  286.             return
  287.         event = ThreadFinishedEvent(owner.GetId()) 
  288.         owner.GetEventHandler().AddPendingEvent(event)                 
  289.     ThreadFinished = staticmethod(ThreadFinished)
  290.     
  291.     def ThreadUpdateStatus(owner, text, append=True):
  292.         if owner.terminating:            
  293.             return
  294.         event = ThreadUpdateStatusEvent(owner.GetId(), text, append)             
  295.         owner.GetEventHandler().AddPendingEvent(event)                    
  296.     ThreadUpdateStatus = staticmethod(ThreadUpdateStatus)
  297.     
  298.     def OnThreadFinished(self, event):
  299.         self.buttonSave.Enable(True)
  300.         self.throbber.Rest()
  301.         self.buttonStop.SetFocus()
  302.         self.buttonStop.SetLabel('&Close')                   
  303.                                
  304.         data = ''
  305.         if self._logfile is not None:
  306.             try:
  307.                 # read last 30000 bytes form the log file 
  308.                 # as our edit control is incapable of displaying more
  309.                 maxsize = 29000
  310.                 flog = file(self._logfile, 'rt')
  311.                 flog.seek(0, 2)
  312.                 size = flog.tell()
  313.                 if size > maxsize:
  314.                     flog.seek(-maxsize, 2)
  315.                 else:
  316.                     flog.seek(0, 0)
  317.                 data = flog.read()
  318.             except Exception, e:
  319.                 print 'Could not read from log file %s. Error: %s' % (self._logfile, str(e))
  320.                 
  321.         # replace cygwin-like pathes with windows-like
  322.         data = data.replace('/', '\').replace('I\O', 'I/O')  
  323.         data = Utils.ReformatLog(data, win32api.GetVersionEx()[0] >= 5)
  324.         
  325.         if len(data.splitlines()) > 1:
  326.             self.ThreadUpdateStatus(self, data, False)
  327.         
  328.         if not self._cancelled:    
  329.            self.ThreadUpdateStatus(self, "n-------------------nCompletedn-------------------n")                  
  330.         else:
  331.            self.ThreadUpdateStatus(self, "n-------------------nCommand has been interrupted...n-------------------n")        
  332.             
  333.         win32api.PostMessage(self.textCtrlStatus.GetHandle(), win32con.EM_SCROLLCARET, 0, 0)
  334.         self.textCtrlStatus.SetInsertionPointEnd()                        
  335.         self.textCtrlStatus.ShowPosition(self.textCtrlStatus.GetLastPosition())                
  336.  
  337.         
  338.         try:                
  339.             self._returnCode = self._proc.wait(_WAIT_TIMEOUT)            
  340.         except:            
  341.             self._returnCode = -1
  342.                         
  343.         if (self._notify_params is not None) and (not self._cancelled):                                
  344.             Utils.ShowBalloon(self._returnCode, self._notify_params)
  345.                                 
  346.         # close the window automatically if requested
  347.         if self._autoClose and 
  348.            (self._closeRetCode is None or self._closeRetCode == self._returnCode):             
  349.             time.sleep(0)
  350.             e = wxCommandEvent(wxEVT_COMMAND_BUTTON_CLICKED, self.buttonStop.GetId())
  351.             self.buttonStop.AddPendingEvent(e)                                                
  352.                     
  353.     def OnThreadUpdateStatus(self, event): 
  354.         ctrl = self.textCtrlStatus               
  355.         lastPos = ctrl.GetLastPosition()
  356.         text = Utils.ReplaceClamAVWarnings(event.text)
  357.         if event.append == True:            
  358.             # Check if we reached 30000 characters
  359.             # and need to purge topmost line            
  360.             if lastPos + len(text) + _NEWLINE_LEN >= 30000:
  361.                 ctrl.Clear()            
  362.             # detect progress message in the new text
  363.             #print_over = re.search('[[d ]?[d ]?d?%?[|/-\*]?]', 
  364.             print_over = re.search('[( {0,2}d{1,3}%)?[|/-\*]?]', 
  365.                 ctrl.GetRange(self._previousStart, lastPos)) is not None                                
  366.             if print_over:                
  367.                 # prevent form blinking text by disabling richedit selection here
  368.                 win32api.SendMessage(ctrl.GetHandle(),Utils.EM_HIDESELECTION, 1, 0)
  369.                 # replace the text 
  370.                 ctrl.Replace(self._previousStart, ctrl.GetLastPosition(), text)               
  371.                 
  372.                 win32api.PostMessage(self.textCtrlStatus.GetHandle(), win32con.EM_SCROLLCARET, 0, 0)
  373.                 lastPos = self._previousStart
  374.             else:                    
  375.                 ctrl.AppendText(text)                    
  376.             # highlight FOUND entries in red                
  377.             if text.endswith(' FOUNDn'):
  378.                 ctrl.SetStyle(lastPos, ctrl.GetLastPosition() - 1, 
  379.                     wxTextAttr(colText = wxColour(128,0,0), 
  380.                         font = wxFont(0, wxDEFAULT, wxNORMAL, wxBOLD, False)))
  381.         else:
  382.             ctrl.Clear()
  383.             ctrl.SetDefaultStyle(wxTextAttr(wxNullColour))
  384.             ctrl.SetValue(text)            
  385.         
  386.         # this is thread unsafe however it doesn't matter as only one thread writes 
  387.         # to the status window
  388.         self._previousStart = lastPos
  389.             
  390.         
  391.     def GetExitCode(self):
  392.         return self._returnCode 
  393.    
  394.     def _SpawnProcess(self, cmd, priority):
  395.         # initialise environment var TMPDIR
  396.         try:
  397.             if os.getenv('TMPDIR') is None:
  398.                 os.putenv('TMPDIR', tempfile.gettempdir().replace('\', '/'))
  399.                 #           re.sub('([A-Za-z]):[/\\]', r'/cygdrive/1/', 
  400.                 #           tempfile.gettempdir()).replace('\', '/'))
  401.             #Utils.SetCygwinTemp()
  402.         except Exception, e:
  403.             print str(e)            
  404.             
  405.         # check that we got the command line        
  406.         if cmd is None:   
  407.             raise Process.ProcessError('Could not start process. No Command Line specified')                                                     
  408.         
  409.         # start our process    
  410.         try:                
  411.             # check if the file exists first
  412.             executable = cmd.split('" ' ,1)[0].lstrip('"')
  413.             if not os.path.exists(executable):
  414.                 raise Process.ProcessError('Could not start process.n%snFile does not exist.' % executable)                
  415.             # create our stdout implementation that updates status window                
  416.             self._out = StatusUpdateBuffer(self, self.ThreadUpdateStatus, self.ThreadFinished)                                                    
  417.             self._proc = Process.ProcessProxy(cmd, stdout=self._out, stderr=self._out, priority=priority)                                                                
  418.             self._proc.wait(_WAIT_NOWAIT)
  419.         except Exception, e:             
  420.             if isinstance(e, Process.ProcessError):
  421.                 if e.errno != Process.ProcessProxy.WAIT_TIMEOUT:                                       
  422.                     raise Process.ProcessError('Could not start process:n%snError: %s' % (cmd, str(e)))                     
  423.             else:
  424.                 raise Process.ProcessError('Could not start process:n%snError: %s' % (cmd, str(e)))
  425.     def OnInitDialog(self, event):
  426.         # start animation
  427.         # we need to have our window drawn before that
  428.         # to display transparent animation properly
  429.         # therefore start it in OnInitDialog   
  430.         self.throbber.Start()
  431.         win32gui.SetForegroundWindow(self.GetHandle())
  432.         event.Skip()
  433.         
  434.     def OnClickURL(self, event):
  435.         if event.GetMouseEvent().LeftIsDown():
  436.             url = self.textCtrlStatus.GetRange(event.GetURLStart(), event.GetURLEnd())
  437.             wxDialogUtils.wxGoToInternetUrl(url)
  438.         event.Skip()
  439.