sync tools 同步小工具(tkinter)

前言

之前写了一个同步的脚本 同步文件小工具_sync (python实现),觉得还挺好用。有个问题就是,它只能在安装了python的电脑上使用。但是我觉得身边其他没学过python的朋友估计也会用上,所以就把它打包了。时间有限,只用简单的tkinter实现,界面有点丑,花了2个小时从原来的脚本改过来。
本着开源的原则,文末会给上所有代码。也希望前端大佬可以包装得好看点。
【注意】请不要将此软件用于商业或者其他盈利性用途。


sync tools 同步小工具(tkinter)
sync tools 同步小工具(tkinter)

下载地址



使用方法

先 【选择原始目录】,
然后 【选择目标目录】。

就可以 【分析比较】,
然后如果要同步则 【确定同步】,由于是不可逆操作,这里还会弹出一个框询问您是否确定同步。



源代码

# -*- coding: UTF-8 -*-
"""
syncTools GUI windows版本

@ Author: Andy Dennis
@ Date: 2021.12.20
@ Detail: V1.0
可以实现 windows 64位机器的两个目录下 所有目录和文件 的同步。

参考代码:
https://blog.csdn.net/weixin_43850253/article/details/121276204
"""
import shutil
import os
from tqdm import tqdm
import time
import tkinter as tk
from tkinter import filedialog, messagebox
import tkinter.font as tf
import webbrowser


class SyncTools:
    def __init__(self, text_edit):
        self.text_edit = text_edit  # 输出信息的框
        self.compare_status = False

    # 设置compare状态
    def set_compare_status(self, status:bool):
        self.compare_status = status

    # 返回compare状态
    def get_compare_status(self):
        return self.compare_status

    # 比较目录
    def compare_operation(self, d_a:str, d_b:str):
        """
        同步目录: d_a -> d_b
        """
        self.add_dir_lt = []  # 增加的目录列表
        self.del_dir_lt = []  # 删除的目录列表
        self.add_file_lt = []      # 增加的文件列表
        self.edit_file_lt = []     # 修改的文件列表
        self.del_file_lt = []      # 删除的文件列表
        self.dir_origin = d_a      # 原始目录
        self.dir_target = d_b      # 目标目录
        self.same_flag = True      # 两个目录是否相同 
        
        start_compare_time = time.time()
        self.compare_directory(d_a=d_a, d_b=d_b)
        end_compare_time = time.time()

        self.text_edit_add('---> 比较目录时长花费: {} 秒\n'.format(round(end_compare_time - start_compare_time, 3)))
        
        if len(self.add_dir_lt) == 0 and len(self.del_dir_lt) == 0 and \
            len(self.add_file_lt) == 0 and len(self.del_file_lt) == 0 and \
                len(self.edit_file_lt) == 0:
            self.text_edit_add(' 两个目录已经是相同的啦, 您不需要再进行操作~~~')
        else:
            self.show_tip()
        self.set_compare_status(True)


    # 执行同步操作
    def sync_operation(self):
        """
        同步目录: d_a -> d_b
        """
        if len(self.add_dir_lt) == 0 and len(self.del_dir_lt) == 0 and \
            len(self.add_file_lt) == 0 and len(self.del_file_lt) == 0 and \
                len(self.edit_file_lt) == 0:
            self.text_edit_add(' 两个目录已经是相同的啦, 您不需要再进行同步~~~')
            return

        start_op_time = time.time()
        self.start_operation()
        end_op_time = time.time()
        self.text_edit_add('---> 执行操作花费: {} 秒'.format(round(end_op_time - start_op_time, 3)))
        self.set_compare_status(False)


    # 比较目录差别, 并存储下来需要的操作
    def compare_directory(self, d_a:str, d_b:str):
        d_a_set = set(os.listdir(d_a))
        d_b_set = set(os.listdir(d_b))

        # a目录比 b 目录多的, 目录 b 待添加项
        d_a_more_item_lt = []
        # b目录比 a 目录多的
        d_a_more_item_lt = []
        # 相同的部分需要比较文件大小等, 有可能进行了修改
        path_same_item_lt = []

        d_a_more_item_lt = list(d_a_set - d_b_set)
        d_b_more_item_lt = list(d_b_set - d_a_set)
        path_same_item_lt = list(d_a_set & d_b_set)

        # 处理 a 目录多的
        for item in d_a_more_item_lt:
            item_path = '{}/{}'.format(d_a, item)
            if os.path.isdir(item_path):  
                # 如果是目录, 那么无疑该目录下的所有文件都是要添加的
                self.add_dir_lt.append(item_path.replace(self.dir_origin, self.dir_target))
                self.add_all_director_item(item_path, True)
            else:
                self.add_file_lt.append(item_path)

        # 处理 b 目录多的
        for item in d_b_more_item_lt:
            item_path = '{}/{}'.format(d_b, item)
            if os.path.isdir(item_path):  
                # 如果是目录, 那么无疑该目录下的所有文件都是要添加的
                self.del_dir_lt.append(item_path)
                self.add_all_director_item(item_path, False)
            else:
                self.del_file_lt.append(item_path)

        # 处理一样的
        for item in path_same_item_lt:
            item_a_path = '{}/{}'.format(d_a, item)
            item_b_path = '{}/{}'.format(d_b, item)
            if os.path.isdir(item_a_path):  
                # 如果是目录, 那么递归处理
                self.compare_directory(d_a=item_a_path, d_b=item_b_path)
            else:
                time_a = os.path.getmtime(item_a_path)
                time_b = os.path.getmtime(item_b_path)
                if os.path.getsize(item_a_path) == os.path.getsize(item_b_path) and \
                    abs(time_a - time_b) <= 2:
                    # 两个文件一样大, 如果修改时间相差小于2秒
                    pass
                else:
                    self.edit_file_lt.append([item_a_path, item_b_path])


    # 将该目录下的所有目录和文件添加到待创建和待复制的任务列表中
    def add_all_director_item(self, dir_path:str, is_add:bool):
        for item in os.listdir(dir_path):
            item_path = '{}/{}'.format(dir_path, item)
            if os.path.isdir(item_path):
                if is_add:
                    self.add_dir_lt.append(item_path.replace(self.dir_origin, self.dir_target))
                self.add_all_director_item(item_path, is_add)
            else:
                if is_add:
                    self.add_file_lt.append(item_path)
                else:
                    self.del_file_lt.append(item_path)

    # 展示即将要进行的操作以及询问是否继续
    def show_tip(self):
        self.text_edit_add('原始目录: {}'.format(self.dir_origin))
        self.text_edit_add('目标目录: {}'.format(self.dir_target))
        
        self.text_edit_add('\n 点击确定同步后 目标目录 将与 原始目录一样\n')
        self.print_operation_lt(self.add_dir_lt, '要创建的目录')
        self.print_operation_lt(self.del_dir_lt, '要删除的目录')
        self.print_operation_lt(self.add_file_lt, '要复制的文件')
        self.print_operation_lt(self.del_file_lt, '要删除的文件')
        self.print_operation_lt(self.edit_file_lt, '要修改的文件')
        

    # 更好的打印项目提示
    def print_operation_lt(self, lt:list, tip:str):
        if len(lt) > 0:
            self.text_edit_add(' {} '.format(tip).center(50, '-'))
            for item in lt:
                if isinstance(item , list): # 修改文件
                    self.text_edit_add(' {} -> {}'.format(item[0], item[1]))
                else:
                    self.text_edit_add('{}{}'.format(' ' * 4, item))
            self.text_edit_add('')


    # 开始执行操作
    def start_operation(self):
        self.text_edit_add('\n ^_^开始操作...')

        if len(self.add_dir_lt) > 0:
            self.text_edit_add('正在创建目录...')
            for item in self.add_dir_lt:
                os.makedirs(item)
            self.text_edit_add('')

        
        if len(self.add_file_lt) > 0:
            self.text_edit_add('正在复制文件...')
            for item in self.add_file_lt:
                item_target = item.replace(self.dir_origin, self.dir_target)
                shutil.copyfile(item, item_target)
                # 设置文件的访问时间和修改时间
                self.set_a_m_time(item, item_target)
                self.text_edit_add('copy {} done'.format(item.split('/')[-1]))
            self.text_edit_add('')
        
        if len(self.del_file_lt) > 0:
            self.text_edit_add('正在删除文件...')
            for item in self.del_file_lt:
                os.remove(item)
                self.text_edit_add('delete {} done'.format(item.split('/')[-1]))
            self.text_edit_add('')

        if len(self.edit_file_lt) > 0:
            self.text_edit_add('正在修改文件...')
            for item in self.edit_file_lt:
                os.remove(item[1])
                shutil.copyfile(item[0], item[1])
                # 设置文件的访问时间和修改时间
                self.set_a_m_time(item[0], item[1])
                self.text_edit_add('edit {} done'.format(item[0].split('/')[-1]))
            self.text_edit_add('')

        if len(self.del_dir_lt) > 0:
            self.text_edit_add('正在删除目录...')
            for item in self.del_dir_lt:
                shutil.rmtree(item)
        self.text_edit_add(' 执行结束 done!')

    # 设置文件的访问时间和修改时间
    def set_a_m_time(self, item_o, item_t):
        # 设置它们的修改时间一样
        st_item_o = os.stat(item_o)
        os.utime(item_t, (st_item_o.st_atime, st_item_o.st_mtime))

    # 输出框添加输出内容
    def text_edit_add(self, text_str, newline=True):
        if newline:
            text_str += '\n'
        self.text_edit.insert('end', text_str)



# 选择原始目录
def select_origin_dir():
    global origin_dir
    global target_dir

    fp = filedialog.askdirectory(title=u'选择原始目录')
    try:
        if fp is not None and len(fp) > 0:
            origin_dir = fp
            # print('origin_dir: ', origin_dir)

            text_edit.delete('1.0','end')
            text_edit.insert('end', 'origin_dir: {}\n'.format(origin_dir))
            if len(target_dir) > 0:
                text_edit.insert('end', 'target_dir: {}\n'.format(target_dir))
            # tk.messagebox.showinfo('success', '选择原始目录成功')
        else:
            tk.messagebox.showerror('error', '选择原始目录失败')
    except:
        tk.messagebox.showerror('error', '选择原始目录失败')


# 选择目标目录
def select_target_dir():
    global target_dir
    global origin_dir

    if len(origin_dir) == 0:
        tk.messagebox.showerror('error', '请选择好原始目录')
        return

    fp = filedialog.askdirectory(title=u'选择目标目录')
    try:
        if fp is not None and len(fp) > 0:
            target_dir = fp
            # print('target_dir: ', target_dir)

            text_edit.delete('1.0','end')
            text_edit.insert('end', 'origin_dir: {}\n'.format(origin_dir))
            text_edit.insert('end', 'target_dir: {}\n'.format(target_dir))
            # tk.messagebox.showinfo('success', '选择目标目录成功')
        else:
            tk.messagebox.showerror('error', '选择目标目录失败')
    except:
        tk.messagebox.showerror('error', '选择目标目录失败')


# 比较两个目录之间的差别
def compare_dir():
    global origin_dir
    global target_dir
    global sync_tool

    if len(origin_dir) == 0 or len(target_dir) == 0:
        tk.messagebox.showerror('error', '请选择好原始和目标目录')
        return
    sync_tool.compare_operation(origin_dir, target_dir)


# 执行同步操作
def sync_operation():
    global origin_dir
    global target_dir
    global sync_tool

    if len(origin_dir) == 0 or len(target_dir) == 0:
        tk.messagebox.showerror('error', '请选择好原始和目标目录')
        return

    if sync_tool.get_compare_status() is None or sync_tool.get_compare_status() == False:
        tk.messagebox.showerror('error', '请先点击比较目录按钮')
        return

    if tk.messagebox.askyesno('Verify', 'Do you really want to sync?'):
        sync_tool.sync_operation()
    


# 取消同步操作
def cancel():
    global origin_dir
    global target_dir
    global sync_tool

    origin_dir = ''
    target_dir = ''

    # 清除输出框所有内容
    text_edit.delete('1.0','end')
    sync_tool.set_compare_status(False)


# 此处必须注意,绑定的事件函数中必须要包含event参数
def open_url(event):
    webbrowser.open("https://blog.csdn.net/weixin_43850253/article/details/122056874", new=0)


window = tk.Tk()
# 标题
window.title('sync tools 简易同步小软件 v1.0')
# 窗口尺寸
window.geometry('504x470')
# 不允许重新设置大小
window.resizable('false', 'false')
# 设置背景颜色
window['background'] = '#90d7ec'


# 全局目录
origin_dir = ''
target_dir = ''

bt1 = tk.Button(window, text='选择原始目录', width=16, height=1, font=tf.Font(size=12), command=select_origin_dir)
bt1.place(x=30, y=30)
bt2 = tk.Button(window, text='选择目标目录', width=16, height=1, font=tf.Font(size=12), command=select_target_dir)
bt2.place(x=330, y=30)


text_edit = tk.Text(window, width=60, height=20, bg='bisque', font=tf.Font(size=12), fg='blue')
text_edit.place(x=10, y=80)
sync_tool = SyncTools(text_edit)


bt3 = tk.Button(window, text='分析比较', width=12, height=1, font=tf.Font(size=12), command=compare_dir)
bt3.place(x=30, y=420)

bt4 = tk.Button(window, text='确定同步', width=12, height=1, font=tf.Font(size=12), command=sync_operation)
bt4.place(x=190, y=420)

bt5 = tk.Button(window, text='取消执行', width=12, height=1, font=tf.Font(size=12), command=cancel)
bt5.place(x=344, y=420)

lb1 = tk.Label(window, text='点我获取帮助', font=tf.Font(size=10),
                   fg='purple', background='#90d7ec')
lb1.place(x=410, y=450)
lb1.bind("<Button-1>", open_url)

window.mainloop()
上一篇:WPF教程六:理解WPF中的事件


下一篇:DevExpress.XtraGrid.GridControl控件使用