前言
之前写了一个同步的脚本 同步文件小工具_sync (python实现),觉得还挺好用。有个问题就是,它只能在安装了python的电脑上使用。但是我觉得身边其他没学过python的朋友估计也会用上,所以就把它打包了。时间有限,只用简单的tkinter实现,界面有点丑,花了2个小时从原来的脚本改过来。
本着开源的原则,文末会给上所有代码。也希望前端大佬可以包装得好看点。
【注意】请不要将此软件用于商业或者其他盈利性用途。
下载地址
使用方法
先 【选择原始目录】,
然后 【选择目标目录】。
就可以 【分析比较】,
然后如果要同步则 【确定同步】,由于是不可逆操作,这里还会弹出一个框询问您是否确定同步。
源代码
# -*- 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()