houtianze/bypy

View on GitHub
bypy/gui.py

Summary

Maintainability
F
3 days
Test Coverage
#!/usr/bin/env python
# coding=utf-8

# A simple GUI for bypy, using Tkinter

# from __future__ imports must occur at the beginning of the file
from __future__ import unicode_literals
from __future__ import print_function
from __future__ import division

import sys
import threading

vi = sys.version_info
if vi[0] == 2:
    import Tkinter as tk
    import tkFileDialog
    import tkMessageBox
    import ttk
elif vi[0] == 3:
    import tkinter as tk
    from tkinter import filedialog as tkFileDialog, messagebox as tkMessageBox, ttk

from . import const
from . import monkey
from .util import (get_pcs_path)
from .tkutil import (
    ColorMap, fgtag, bgtag,
    Stretch, GridStyle, MyLogText,
    centerwindow)
from .bypy import ByPy

GuiTitle = "Bypy GUI"

class RemoteListGui(tk.Toplevel):
    # remotepath is the partial path,
    # NOT including the '/apps/bypy' in front
    def __init__(self, master, byp, remotepath = ''):
        tk.Toplevel.__init__(self, master)

        self.byp = byp
        self.rpath = get_pcs_path(remotepath)
        self.result = ''

        self.transient(master)
        self.master = master
        title = 'Baidu: ' + self.rpath
        self.title(title)

        self.CreateWidgets()
        self.geometry('400x300+0+0')
        self.GetRemote()

        self.grab_set()

    def GetRemoteAct(self, r, args):
        self.wList.delete(0, tk.END)
        if self.rpath.strip('/') != const.AppPcsPath.strip('/'):
            self.wList.insert(tk.END, '..')
        try:
            j = r.json()
            for f in j['list']:
                fullpath = f['path']
                relpath = fullpath.split('/')[-1]
                self.wList.insert(tk.END,
                    relpath + '/' if f['isdir'] else relpath)

            return const.ENoError
        except:
            return const.EException

    def GetRemote(self):
        pars = {
            'method' : 'list',
            'path' : self.rpath,
            'by' : 'name',
            'order' : 'asc' }

        result = self.byp._get(
            const.PcsUrl + 'file', pars, self.GetRemoteAct)

        if result == const.ENoError:
            self.title(self.rpath)
        else:
            if self.rpath.strip('/') == const.AppPcsPath.strip('/'):
                err = "Can't retrieve Baidu PCS directories!\n" + \
                    "Maybe the network is down?\n" + \
                    "You can still manually input the remote path though" + \
                    "(But, I doubt it will work)"
                tkMessageBox.showerror(GuiTitle, err)
                self.Bye()
            else:
                self.rpath = const.get_pcs_path('')
                self.GetRemote()

    def Bye(self, result = ''):
        self.result = result
        self.master.focus_set()
        self.destroy()

    def Delete(self, selected):
        if tkMessageBox.askyesno(
            title = GuiTitle,
            message = "Are you sure you want to delete '{}' at Baidu?".format(selected),
            parent = self):
            rpath = '/'.join([self.rpath, selected])
            r = self.byp._delete(rpath)
            if r == const.ENoError:
                self.wList.delete(tk.ANCHOR)
            else:
                tkMessageBox.showerror(
                    title = GuiTitle,
                    message = "Fail to delete '{}' at Baidu".format(selected),
                    parent = self)

    def Select(self, event):
        if event.widget == self.wOK:
            self.Bye(self.rpath[len(const.AppPcsPath):])
        elif event.widget == self.wList:
            selected = ''
            iet = int(event.type) # i don't know why, but it seems needed
            if iet == 4: # mouse event. mouse is special or not?
                selected = self.wList.get(self.wList.nearest(event.y))
            elif iet == 2: # KeyPress
                selected = self.wList.get(tk.ACTIVE)

            if iet == 4 or \
                (iet == 2 and event.keysym == 'Return'):
                if selected[-1] == '/':
                    self.rpath = '/'.join([self.rpath, selected[:-1]])
                    self.GetRemote()
                elif selected == '..':
                    self.rpath = '/'.join(self.rpath.split('/')[:-1])
                    self.GetRemote()
                else:
                    self.Bye('/'.join([self.rpath, selected])[len(const.AppPcsPath):])
            elif iet == 2 and event.keysym == 'Delete':
                # don't handlle this, not so rational usage
                if selected != '..':
                    self.Delete(selected)

    def CreateWidgets(self):
        self.grid_columnconfigure(0, weight = 1)
        self.grid_rowconfigure(0, weight = 1)
        self.wList = tk.Listbox(self)
        self.wList.grid(sticky = Stretch, **GridStyle)
        self.wList.bind('<Double-Button-1>', self.Select)
        self.wList.bind('<Return>', self.Select)
        self.wList.bind('<Delete>', self.Select)
        self.wOK = tk.Button(self, text = 'OK', default = tk.ACTIVE)
        self.wOK.grid(row = 1, column = 0, sticky = tk.E + tk.W, **GridStyle)
        self.wOK.bind('<Button-1>', self.Select)
        self.wOK.bind('<Return>', self.Select)

        self.wList.focus_set()

        self.protocol("WM_DELETE_WINDOW", lambda: ())

class BypyGui(tk.Frame):
    # function remapping / hijacking
    # pr and prcolor functions in console and GUI are different:
    #   in console: pr is the foundamental function, prcolor calls it
    #   in GUI: prcolor is the foundamental fucntion, pr calls it
    def __init__(self, master = None):
        tk.Frame.__init__(self, master)

        self.master.title(GuiTitle)
        self.grid(sticky = Stretch)

        self.byp = None

        self.threadrunning = False

        self.localPath = tk.StringVar()
        self.localPath.set('/tmp')
        self.remotePath = tk.StringVar()
        self.bLog = tk.IntVar()
        self.bLog.set(1)
        self.bSyncDelete = tk.IntVar()
        self.bSyncDelete.set(0)
        self.progress = tk.IntVar()
        self.progress.set(0)
        self.maxProgress = 1000

        self.CreateWidgets()

        monkey.setgui(self)

        self.initbypy()

    def CreateWidgets(self):
        self.master.grid_columnconfigure(0, weight = 1)
        self.master.grid_rowconfigure(1, weight = 1)
        self.grid_columnconfigure(1, weight = 1)
        self.grid_rowconfigure(4, weight = 1)

        z = tk.Label(self, text = 'Baidu: ' + const.AppPcsPath)
        z.grid(row = 0, column = 0, **GridStyle)
        self.wRemotePath = tk.Entry(self, textvariable = self.remotePath)
        self.wRemotePath.grid(row = 0, column = 1, sticky = Stretch, **GridStyle)
        self.wRemoteSelect = tk.Button(self, text = 'R', underline = 0)
        self.wRemoteSelect.grid(row = 0, column = 2, **GridStyle)
        self.wRemoteSelect.bind('<Button-1>', self.selectremotepath)
        self.bind_all('<Alt-r>', self.selectremotepath)

        z = tk.Label(self, text = 'Local 本地')
        z.grid(**GridStyle)
        self.wLocalPath = tk.Entry(self, textvariable = self.localPath)
        self.wLocalPath.grid(row = 1, column = 1, sticky = Stretch, **GridStyle)
        self.wLocalSelect = tk.Button(self, text = 'L', underline = 0)
        self.wLocalSelect.grid(row = 1, column = 2, **GridStyle)
        self.wLocalSelect.bind('<Button-1>', self.selectlocalpath)
        self.bind_all('<Alt-l>', self.selectlocalpath)

        self.OpFrame = tk.Frame(self)
        self.OpFrame.grid(row = 2, columnspan = 3, sticky = Stretch, **GridStyle)
        self.OpFrame.grid_columnconfigure(0, weight = 1)
        self.OpFrame.grid_columnconfigure(1, weight = 1)
        self.OpFrame.grid_columnconfigure(2, weight = 1)
        self.OpFrame.grid_columnconfigure(3, weight = 1)
        self.OpFrame.grid_columnconfigure(3, weight = 1)
        self.OpFrame.grid_columnconfigure(4, weight = 1)

        self.wSyncUp = tk.Button(self.OpFrame, text = 'Sync Up 上传同步', underline = 5)
        self.wSyncUp.grid(sticky = Stretch, **GridStyle)
        self.wSyncUp.bind('<Button-1>', self.startsyncup)
        self.bind_all('<Alt-u>', self.startsyncup)

        self.wUpload = tk.Button(self.OpFrame, text = 'Upload 上传')
        self.wUpload.grid(row = 0, column = 1, sticky = Stretch, **GridStyle)
        self.wUpload.bind('<Button-1>', self.startupload)

        self.wSyncDown = tk.Button(self.OpFrame, text = 'Sync Down 下载同步', underline = 5)
        self.wSyncDown.grid(row = 0, column = 2, sticky = Stretch, **GridStyle)
        self.wSyncDown.bind('<Button-1>', self.startsyncdown)
        self.bind_all('<Alt-d>', self.startsyncdown)

        self.wDownload = tk.Button(self.OpFrame, text = 'Download 下载')
        self.wDownload.grid(row = 0, column = 3, sticky = Stretch, **GridStyle)
        self.wDownload.bind('<Button-1>', self.startdownload)

        self.wSyncDelete = tk.Checkbutton(self.OpFrame, text = 'Sync Del 同步删除', underline = 7, variable = self.bSyncDelete)
        self.wSyncDelete.configure(foreground = 'red')
        self.wSyncDelete.grid(row = 0, column = 4, sticky = Stretch, **GridStyle)
        self.bind_all('<Alt-l>', lambda e: self.bSyncDelete.set(1 if self.bSyncDelete.get() == 0 else 0))
        self.wEnableLog = tk.Checkbutton(self.OpFrame, text = 'Log 日志', underline = 2, variable = self.bLog)
        self.wEnableLog.grid(row = 0, column = 5, sticky = Stretch, **GridStyle)
        self.bind_all('<Alt-g>', lambda e: self.bLog.set(1 if self.bLog.get() == 0 else 0))

        self.wCompare = tk.Button(self.OpFrame, text = 'Compare Dir 比较目录')
        self.wCompare.grid(row = 1, column = 0, sticky = Stretch, **GridStyle)
        self.wCompare.bind('<Button-1>', self.startcompare);

        self.wProgressBar = ttk.Progressbar(
            self, maximum = self.maxProgress, variable = self.progress,
            mode = 'determinate')
        self.wProgressBar.grid(row = 3, column = 0, columnspan = 3, sticky = Stretch, **GridStyle)
        self.wLog = MyLogText(self)
        self.wLog.grid(row = 4, column = 0, columnspan = 3, sticky = Stretch, **GridStyle)

        self.wClearLog = tk.Button(self.OpFrame, text = 'Clear Log 清空日志')
        self.wClearLog.grid(row = 1, column = 5, sticky = Stretch, **GridStyle)
        self.wClearLog.bind('<Button-1>',
            lambda e: self.wLog.delete('1.0', tk.END))

        #self.wLog.tag_add(fgtag(''))
        #self.wLog.tag_add(bgtag(''))
        for k, v in ColorMap.items():
            ft = fgtag(v)
            bt = bgtag(v)
            #self.wLog.tag_add(ft)
            self.wLog.tag_config(ft, foreground = v)
            #self.wLog.tag_add(bt)
            self.wLog.tag_config(bt, background = v)

        centerwindow(self.master)

    def initbypy(self):
        self.byp = ByPy(verbose = 1)

    def selectlocalpath(self, *args):
        self.localPath.set(
            tkFileDialog.askopenfilename(
                title = "Please select a local file " + \
                    "(remove the file name later if you " + \
                    "want to select a directory)",
                initialdir = self.wLocalPath,
                parent = self))

    def selectremotepath(self, *args):
        remoteList = RemoteListGui(self, self.byp, self.remotePath.get())
        centerwindow(remoteList)
        remoteList.wait_window(remoteList)
        self.remotePath.set(remoteList.result)

    def syncupproc(self, lpath, rpath, delete):
        self.byp.syncup(lpath, rpath, delete)
        self.threadrunning = False

    def startsyncup(self, *args):
        if not self.threadrunning:
            self.threadrunning == True
            threading.Thread(target = self.syncupproc,
                args = (
                    self.localPath.get(),
                    self.remotePath.get(),
                    self.bSyncDelete.get())).start()

    def uploadproc(self, lpath, rpath):
        self.byp.upload(lpath, rpath)
        self.threadrunning = False

    def startupload(self, *args):
        if not self.threadrunning:
            self.threadrunning == True
            threading.Thread(target = self.uploadproc,
                args = (
                    self.localPath.get(),
                    self.remotePath.get())).start()

    def syncdownproc(self, rpath, lpath, delete):
        self.byp.syncdown(rpath, lpath, delete)
        self.threadrunning = False

    def startsyncdown(self, *args):
        if not self.threadrunning:
            self.threadrunning == True
            threading.Thread(target = self.syncdownproc,
                args = (
                    self.remotePath.get(),
                    self.localPath.get(),
                    self.bSyncDelete.get())).start()

    def downloadproc(self, rpath, lpath):
        if len(rpath) == 0 or rpath[-1] == '/':
            self.byp.downdir(rpath, lpath)
        else:
            self.byp.downfile(rpath, lpath)
        self.threadrunning = False

    def startdownload(self, *args):
        if not self.threadrunning:
            self.threadrunning == True
            threading.Thread(target = self.downloadproc,
                args = (
                    self.remotePath.get(),
                    self.localPath.get())).start()

    def compareproc(self, rpath, lpath):
        self.byp.compare(rpath, lpath)
        self.threadrunning = False

    def startcompare(self, *args):
        if not self.threadrunning:
            self.threadrunning == True
            threading.Thread(target = self.compareproc,
                args = (
                    self.remotePath.get(),
                    self.localPath.get())).start()

def main():
    tkRoot = tk.Tk()
    ui = BypyGui(tkRoot)
    ui.mainloop()

if __name__ == '__main__':
    main()

def unused():
    ''' just prevent unused warnings '''
    tkMessageBox.showinfo('')

# vim: tabstop=4 noexpandtab shiftwidth=4 softtabstop=4 ff=unix fileencoding=utf-8