Source

mygrsync / mygrsyncdlg.py

Full commit
#Boa:MiniFrame:grsyncdlg
# -*- coding: utf-8 -*-

import sys
import os
import subprocess
import threading
import shlex
import locale
import time
import traceback
import wx
import wx.stc

def create(parent):
    return grsyncdlg(parent)

[wxID_GRSYNCDLG, wxID_GRSYNCDLGBTNCLOSE, wxID_GRSYNCDLGBTNHELP, 
 wxID_GRSYNCDLGBTNSTOP, wxID_GRSYNCDLGBTNSYNC, wxID_GRSYNCDLGCHKPIN, 
 wxID_GRSYNCDLGCHK_A, wxID_GRSYNCDLGCHK_R, wxID_GRSYNCDLGCHK_T, 
 wxID_GRSYNCDLGCHK_V, wxID_GRSYNCDLGCHK_Z, wxID_GRSYNCDLGCHK__DELETE, 
 wxID_GRSYNCDLGCHK__DRY_RUN, wxID_GRSYNCDLGCHK__UPDATE, wxID_GRSYNCDLGPANEL1, 
 wxID_GRSYNCDLGSTATICTEXT1, wxID_GRSYNCDLGSTATICTEXT2, 
 wxID_GRSYNCDLGSTATICTEXT3, wxID_GRSYNCDLGSTATICTEXT4, 
 wxID_GRSYNCDLGSTCSYNCLOG, wxID_GRSYNCDLGTXTDST, wxID_GRSYNCDLGTXTEXCLUDE, 
 wxID_GRSYNCDLGTXTOTHER, wxID_GRSYNCDLGTXTSRC, 
] = [wx.NewId() for _init_ctrls in range(24)]

class grsyncdlg(wx.Frame):
    def _init_coll_boxSizer1_Items(self, parent):
        # generated method, don't edit

        parent.AddWindow(self.panel1, 0, border=0, flag=wx.EXPAND)
        parent.AddWindow(self.stcSyncLog, 1, border=0, flag=wx.EXPAND)

    def _init_sizers(self):
        # generated method, don't edit
        self.boxSizer1 = wx.BoxSizer(orient=wx.VERTICAL)

        self._init_coll_boxSizer1_Items(self.boxSizer1)

        self.SetSizer(self.boxSizer1)

    def _init_ctrls(self, prnt):
        # generated method, don't edit
        wx.Frame.__init__(self, id=wxID_GRSYNCDLG, name=u'grsyncdlg',
              parent=prnt, pos=wx.Point(504, 120), size=wx.Size(719, 569),
              style=wx.DEFAULT_FRAME_STYLE, title=u'grsyncdlg')
        self.SetClientSize(wx.Size(703, 531))
        self.Bind(wx.EVT_CLOSE, self.OnGrsyncdlgClose)

        self.panel1 = wx.Panel(id=wxID_GRSYNCDLGPANEL1, name='panel1',
              parent=self, pos=wx.Point(0, 0), size=wx.Size(703, 112),
              style=wx.TAB_TRAVERSAL)

        self.stcSyncLog = wx.stc.StyledTextCtrl(id=wxID_GRSYNCDLGSTCSYNCLOG,
              name=u'stcSyncLog', parent=self, pos=wx.Point(0, 112),
              size=wx.Size(703, 419), style=0)

        self.staticText1 = wx.StaticText(id=wxID_GRSYNCDLGSTATICTEXT1,
              label=u'SRC', name='staticText1', parent=self.panel1,
              pos=wx.Point(8, 8), size=wx.Size(21, 14), style=0)

        self.txtSRC = wx.TextCtrl(id=wxID_GRSYNCDLGTXTSRC, name=u'txtSRC',
              parent=self.panel1, pos=wx.Point(32, 8), size=wx.Size(320, 22),
              style=0, value=u'')

        self.staticText2 = wx.StaticText(id=wxID_GRSYNCDLGSTATICTEXT2,
              label=u'DST', name='staticText2', parent=self.panel1,
              pos=wx.Point(8, 32), size=wx.Size(23, 14), style=0)

        self.txtDST = wx.TextCtrl(id=wxID_GRSYNCDLGTXTDST, name=u'txtDST',
              parent=self.panel1, pos=wx.Point(32, 32), size=wx.Size(320, 22),
              style=0, value=u'')

        self.staticText4 = wx.StaticText(id=wxID_GRSYNCDLGSTATICTEXT4,
              label=u'exclude dir', name='staticText4', parent=self.panel1,
              pos=wx.Point(8, 64), size=wx.Size(59, 14), style=0)

        self.txtExclude = wx.TextCtrl(id=wxID_GRSYNCDLGTXTEXCLUDE,
              name=u'txtExclude', parent=self.panel1, pos=wx.Point(80, 64),
              size=wx.Size(376, 22), style=0, value=u'')

        self.staticText3 = wx.StaticText(id=wxID_GRSYNCDLGSTATICTEXT3,
              label=u'Other Param', name='staticText3', parent=self.panel1,
              pos=wx.Point(8, 88), size=wx.Size(69, 14), style=0)

        self.txtOther = wx.TextCtrl(id=wxID_GRSYNCDLGTXTOTHER, name=u'txtOther',
              parent=self.panel1, pos=wx.Point(80, 88), size=wx.Size(376, 22),
              style=0, value=u'')

        self.chk__dry_run = wx.CheckBox(id=wxID_GRSYNCDLGCHK__DRY_RUN,
              label=u'--dry-run', name=u'chk__dry_run', parent=self.panel1,
              pos=wx.Point(376, 8), size=wx.Size(79, 14), style=0)
        self.chk__dry_run.SetValue(True)
        self.chk__dry_run.Bind(wx.EVT_CHECKBOX, self.OnChk_x,
              id=wxID_GRSYNCDLGCHK__DRY_RUN)

        self.chk_v = wx.CheckBox(id=wxID_GRSYNCDLGCHK_V, label=u'-v',
              name=u'chk_v', parent=self.panel1, pos=wx.Point(464, 8),
              size=wx.Size(72, 14), style=0)
        self.chk_v.SetValue(True)
        self.chk_v.Bind(wx.EVT_CHECKBOX, self.OnChk_x, id=wxID_GRSYNCDLGCHK_V)

        self.chk_z = wx.CheckBox(id=wxID_GRSYNCDLGCHK_Z, label=u'-z',
              name=u'chk_z', parent=self.panel1, pos=wx.Point(552, 8),
              size=wx.Size(48, 14), style=0)
        self.chk_z.SetValue(True)
        self.chk_z.Bind(wx.EVT_CHECKBOX, self.OnChk_x, id=wxID_GRSYNCDLGCHK_Z)

        self.chk_a = wx.CheckBox(id=wxID_GRSYNCDLGCHK_A, label=u'-a',
              name=u'chk_a', parent=self.panel1, pos=wx.Point(616, 8),
              size=wx.Size(40, 14), style=0)
        self.chk_a.SetValue(True)
        self.chk_a.Bind(wx.EVT_CHECKBOX, self.OnChk_x, id=wxID_GRSYNCDLGCHK_A)

        self.chk__delete = wx.CheckBox(id=wxID_GRSYNCDLGCHK__DELETE,
              label=u'--delete', name=u'chk__delete', parent=self.panel1,
              pos=wx.Point(376, 40), size=wx.Size(79, 14), style=0)
        self.chk__delete.SetValue(False)
        self.chk__delete.Bind(wx.EVT_CHECKBOX, self.OnChk_x,
              id=wxID_GRSYNCDLGCHK__DELETE)

        self.chk__update = wx.CheckBox(id=wxID_GRSYNCDLGCHK__UPDATE,
              label=u'--update', name=u'chk__update', parent=self.panel1,
              pos=wx.Point(464, 40), size=wx.Size(72, 14), style=0)
        self.chk__update.SetValue(True)
        self.chk__update.Bind(wx.EVT_CHECKBOX, self.OnChk_x,
              id=wxID_GRSYNCDLGCHK__UPDATE)

        self.chk_t = wx.CheckBox(id=wxID_GRSYNCDLGCHK_T, label=u'-t',
              name=u'chk_t', parent=self.panel1, pos=wx.Point(552, 40),
              size=wx.Size(48, 14), style=0)
        self.chk_t.SetValue(False)
        self.chk_t.Bind(wx.EVT_CHECKBOX, self.OnChk_x, id=wxID_GRSYNCDLGCHK_T)

        self.chk_r = wx.CheckBox(id=wxID_GRSYNCDLGCHK_R, label=u'-r',
              name=u'chk_r', parent=self.panel1, pos=wx.Point(616, 40),
              size=wx.Size(40, 14), style=0)
        self.chk_r.SetValue(False)
        self.chk_r.Bind(wx.EVT_CHECKBOX, self.OnChk_x, id=wxID_GRSYNCDLGCHK_R)

        self.chkpin = wx.CheckBox(id=wxID_GRSYNCDLGCHKPIN, label=u'pin',
              name=u'chkpin', parent=self.panel1, pos=wx.Point(664, 40),
              size=wx.Size(63, 14), style=0)
        self.chkpin.SetValue(False)
        self.chkpin.Bind(wx.EVT_CHECKBOX, self.OnChkpinCheckbox,
              id=wxID_GRSYNCDLGCHKPIN)

        self.btnHelp = wx.Button(id=wxID_GRSYNCDLGBTNHELP, label=u'?',
              name=u'btnHelp', parent=self.panel1, pos=wx.Point(664, 8),
              size=wx.Size(35, 24), style=0)
        self.btnHelp.Bind(wx.EVT_BUTTON, self.OnBtnHelpButton,
              id=wxID_GRSYNCDLGBTNHELP)

        self.btnsync = wx.Button(id=wxID_GRSYNCDLGBTNSYNC, label=u'RUN ...',
              name=u'btnsync', parent=self.panel1, pos=wx.Point(480, 64),
              size=wx.Size(80, 48), style=0)
        self.btnsync.Bind(wx.EVT_BUTTON, self.OnBtnsyncButton,
              id=wxID_GRSYNCDLGBTNSYNC)

        self.btnStop = wx.Button(id=wxID_GRSYNCDLGBTNSTOP, label=u'STOP',
              name=u'btnStop', parent=self.panel1, pos=wx.Point(576, 64),
              size=wx.Size(48, 48), style=0)
        self.btnStop.Bind(wx.EVT_BUTTON, self.OnBtnStopButton,
              id=wxID_GRSYNCDLGBTNSTOP)

        self.btnClose = wx.Button(id=wxID_GRSYNCDLGBTNCLOSE, label=u'Close',
              name=u'btnClose', parent=self.panel1, pos=wx.Point(640, 64),
              size=wx.Size(51, 48), style=0)
        self.btnClose.SetForegroundColour(wx.Colour(255, 0, 0))
        self.btnClose.Bind(wx.EVT_BUTTON, self.OnBtnCloseButton,
              id=wxID_GRSYNCDLGBTNCLOSE)

        self._init_sizers()

    def __init__(self, parent):
        self._init_ctrls(parent)
        self.init2()

    def init2(self):
        ff = wx.Font(9, wx.SWISS, wx.NORMAL, wx.NORMAL, False, u'Courier New')
        self.txtSRC.SetFont(ff)
        self.txtDST.SetFont(ff)
        self.txtExclude.SetFont(ff)
        self.txtOther.SetFont(ff)
        self.stcSyncLog.StyleSetFont(wx.stc.STC_STYLE_DEFAULT, ff)
        self.stcSyncLog.SetMarginType(1, wx.stc.STC_MARGIN_NUMBER)
        self.stcSyncLog.SetMarginWidth(1, 38)
        self.stcSyncLog.SetLexer(wx.stc.STC_LEX_SQL)
        self.stcSyncLog.SetKeyWords(0, 'sigint sigterm sighup error failed '
                                    'unexpectedly warning vanished deleting '
                                    'inserting sending incremental')
        self.stcSyncLog.StyleSetSpec(wx.stc.STC_SQL_WORD, "fore:#FFFFFF,bold")
        self.stcSyncLog.StyleSetSpec(wx.stc.STC_SQL_WORD, "back:#FF0000")
        self.stcSyncLog.SetKeyWords(1, 'total size dry run')
        self.stcSyncLog.StyleSetSpec(wx.stc.STC_SQL_WORD2, "fore:#FFFFFF,bold")
        self.stcSyncLog.StyleSetSpec(wx.stc.STC_SQL_WORD2, "back:#0000FF")

        nid = wx.NewId()
        self.timer1 = wx.Timer(id=nid, owner=self)
        self.Bind(wx.EVT_TIMER, self.OnTimerUp, id=nid)
        self.timer1.Start(100)

        self.autoscroll = True
        self.sp = []
        self.dataout = []
        self.dataerr = []

    def OnChkpinCheckbox(self, event):
        event.Skip()
        self.autoscroll = not self.autoscroll

    def OnGrsyncdlgClose(self, event):
        event.Skip()
        print ' OnGrsyncdlgClose'
        self.terminalsps()
        self.timer1.Stop()

    def terminalsps(self):
        for sp in self.sp[:]:
            if sp.poll() is None:
                sp.terminate()
                if sp.poll() is None: # XXXX win32 sp may has problem
                    print '  Terminate %s, Failed !!!' % str(sp)
                    try:    # win32 py>=2.7 0:signal.CTRL_C_EVENT 1:signal.CTRL_BREAK_EVENT
                        sp.send_signal(1)
                    except:
                        print '---------\n%s\n---------\n' % traceback.format_exc()
                    try:    # not win32
                        sp.kill()
                    except:
                        if sp in self.sp: self.sp.remove(sp)
                else:
                    print '  Terminate %s, Success' % str(sp)
                    if sp in self.sp: self.sp.remove(sp)
            else:
                print ' sp terminated, remove it'
                if sp in self.sp: self.sp.remove(sp)

    def create_subproc_logthread(self, cmd):
        sp = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE,
                              stderr=subprocess.PIPE,
                              startupinfo=G.startupinfo,
                              creationflags=G.creationflags)
        self.sp.append(sp)

        def thpg(sp, self):
            t0 = time.time()
            self.dataout.append('\n/=========== stdout ===========\\\n')
            while True:
                msg = sp.stdout.readline()
                if sp.poll() is not None and msg == '':
                    try:
                        if sp in self.sp: self.sp.remove(sp)
                        self.dataerr.append('\n|=========== stderr ===========|\n')
                        self.dataerr.append(sp.stderr.read())
                        self.dataerr.append('\n\\=========== theend ===========/')
                        self.dataerr.append('\n Time: %s\n' % (time.time() - t0))
                    except Exception as e:
                        print ' window closed, thread break'
                        break
                    break
                if msg:
                    try:
                        self.dataout.append(msg)
                    except Exception as e:
                        print ' window closed, thread break'
                        break

        th1 = threading.Thread(target=thpg, args=(sp, self, ))
        th1.setDaemon(True)
        th1.start()

    def OnTimerUp(self, event):
        i = len(self.dataout)
        if i != 0:
            data = ''.join(self.dataout[:i])
            del self.dataout[:i]
            self.stcSyncLog.AppendText(data.decode(G.str_encode, 'replace'))
            if self.autoscroll:
                self.stcSyncLog.GotoLine(self.stcSyncLog.GetLineCount())
        i = len(self.dataerr)
        if i != 0:
            data = ''.join(self.dataerr[:i])
            del self.dataerr[:i]
            self.stcSyncLog.AppendText(data.decode(G.str_encode, 'replace'))
            if self.autoscroll:
                self.stcSyncLog.GotoLine(self.stcSyncLog.GetLineCount())

    def getcmd(self):
        cmds = [G._pg_RSYNC]
        if self.chk__dry_run.GetValue(): cmds.append('-n')
        if self.chk_v.GetValue(): cmds.append('-v')
        if self.chk_z.GetValue(): cmds.append('-z')
        if self.chk_a.GetValue(): cmds.append('-a')
        if self.chk__delete.GetValue(): cmds.append('--delete')
        if self.chk__update.GetValue(): cmds.append('--update')
        if self.chk_t.GetValue(): cmds.append('-t')
        if self.chk_r.GetValue(): cmds.append('-r')
        expar = self.txtOther.GetValue().encode(G.str_encode)
        if expar: cmds.append(expar)
        expar = self.txtExclude.GetValue().encode(G.str_encode)
        if expar: cmds.append(' '.join(['--exclude="%s"' % i for i in shlex.split(expar) if i]))

        cmds.append('  ')

        lu = self.txtSRC.GetValue().encode(G.str_encode)
        if lu[-1] != '/': # FUXX
            lu = lu + '/'
        ru = self.txtDST.GetValue().encode(G.str_encode)
        cmds.append('"%s"' % lu)
        if ru[-1] != '/':
            ru = ru + '/'
        cmds.append('"%s"' % ru)

        cmd = ' '.join(cmds)
        return cmd

    def OnChk_x(self, event):
        event.Skip()
        cmd = self.getcmd()
        self.dataout.append(cmd + '\n')

    def OnBtnHelpButton(self, event):
        event.Skip()
        if not hasattr(self, '_rsyncdoc'):
            sp = subprocess.Popen('%s --help -v' % G._pg_RSYNC, shell=True,
                                  stdout=subprocess.PIPE,
                                  startupinfo=G.startupinfo)
            self._rsyncdoc = sp.stdout.read().decode(G.str_encode)
            self._dlg = wx.Dialog(self, title=u"rsync --help", size=wx.Size(640,480),
                                  style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER)
            self._stc = wx.stc.StyledTextCtrl(self._dlg)
            ff = wx.Font(9, wx.SWISS, wx.NORMAL, wx.NORMAL, False, u'Courier New')
            self._stc.StyleSetFont(wx.stc.STC_STYLE_DEFAULT, ff)
            self._stc.SetMarginType(1, wx.stc.STC_MARGIN_NUMBER)
            self._stc.SetMarginWidth(1, 38)
            self._stc.SetSize(self._dlg.GetClientSize())
        if self._dlg.IsShown():
            self._dlg.Hide()
        else:
            self._stc.SetText(self._rsyncdoc)
            self._stc.EmptyUndoBuffer()
            self._dlg.Show()

    def OnBtnsyncButton(self, event):
        if self.sp:
            print 'has rsync sub process running ... '
            return
        cmd = self.getcmd()
        print cmd
        self.stcSyncLog.SetText(time.strftime('Time: %Y-%m-%d %H:%M:%S\n'))
        self.stcSyncLog.AppendText(cmd.decode(G.str_encode) + '\n\n')
        self.create_subproc_logthread(cmd)
        print '---------'

    def OnBtnStopButton(self, event):
        event.Skip()
        self.terminalsps()

    def OnBtnCloseButton(self, event):
        event.Skip()
        self.Close()