<think>我们计划创建一个具有以下功能的GUI应用程序:
1. 允许用户设置源目录(网络路径)和目标目录(本地路径)。
2. 定期扫描源目录,将新增的文件复制到目标目录(只复制新增的,已经复制过的跳过)。
3. 提供简单的图形界面,用户可以修改路径,并启动/停止监控。
我们将使用以下库:
- `tkinter`:用于创建GUI界面。
- `shutil`:用于复制文件。
- `os`:用于路径操作和文件遍历。
- `time`:用于定时扫描。
- `threading`:用于在后台运行扫描任务,避免阻塞GUI。
注意:由于网络路径可能需要认证,我们在复制文件之前先尝试连接。这里我们使用`os.path.exists`来检查路径是否存在,但注意网络路径可能需要先映射为驱动器(或者使用UNC路径直接访问)。对于需要认证的网络路径,我们可以使用`net use`命令(Windows)来建立连接,但为了简化,我们假设网络路径已经可以访问(或已经映射为驱动器)。
步骤:
1. 创建GUI窗口,包含源目录输入框、目标目录输入框、启动按钮和停止按钮。
2. 使用一个后台线程定期扫描源目录,记录已经复制的文件(通过记录文件名或修改时间)。
3. 在扫描时,比较源目录的文件列表和已经复制的文件列表,只复制新增的文件。
为了记录已经复制的文件,我们可以使用一个集合(set)来存储源目录中已经复制的文件的绝对路径。但是,当程序重启后,我们需要持久化记录。这里为了简化,我们每次运行只记录当次运行期间的新增文件。如果需要持久化,可以保存到一个文件中,但这里我们先不做。
另外,为了避免频繁扫描,我们可以设置一个扫描间隔(比如5秒)。
由于网络路径可能断开,我们需要在每次扫描时检查源目录是否可访问。
下面开始编写代码:
</think>以下是一个完整的Python解决方案,结合了文件监控、自动复制和GUI界面功能,满足您的需求:
```python
import os
import shutil
import threading
import time
import tkinter as tk
from tkinter import filedialog, messagebox
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
class FileMonitorHandler(FileSystemEventHandler):
"""监控网络路径变化的处理器"""
def __init__(self, src_path, dst_path, status_var):
super().__init__()
self.src_path = src_path
self.dst_path = dst_path
self.status_var = status_var
self.copied_files = set()
def on_created(self, event):
"""当有新文件创建时触发"""
if not event.is_directory:
file_path = event.src_path
self.copy_new_file(file_path)
def on_modified(self, event):
"""当文件修改时触发"""
if not event.is_directory:
file_path = event.src_path
if os.path.exists(file_path) and file_path not in self.copied_files:
self.copy_new_file(file_path)
def copy_new_file(self, src_file):
"""复制新文件到目标路径"""
try:
# 确保目标目录存在
os.makedirs(self.dst_path, exist_ok=True)
# 获取文件名并构建目标路径
filename = os.path.basename(src_file)
dst_file = os.path.join(self.dst_path, filename)
# 复制文件
shutil.copy2(src_file, dst_file)
self.copied_files.add(src_file)
self.status_var.set(f"已复制新文件: {filename}")
print(f"✅ 已复制: {src_file} → {dst_file}")
except Exception as e:
self.status_var.set(f"复制失败: {str(e)}")
print(f"❌ 复制失败: {src_file} → {str(e)}")
class FileMonitorApp:
"""文件监控GUI应用程序"""
def __init__(self, root):
self.root = root
root.title("网络文件同步工具")
root.geometry("600x400")
root.resizable(True, True)
# 创建变量
self.network_path_var = tk.StringVar(value=r"\\192.168.1.24\Git")
self.local_path_var = tk.StringVar(value=r"D:\downloads\network_files")
self.status_var = tk.StringVar(value="准备就绪")
self.monitoring = False
self.observer = None
# 创建UI组件
self.create_widgets()
def create_widgets(self):
"""创建界面组件"""
# 创建框架
main_frame = tk.Frame(self.root, padx=20, pady=20)
main_frame.pack(fill=tk.BOTH, expand=True)
# 网络路径选择
tk.Label(main_frame, text="网络路径:").grid(row=0, column=0, sticky="w", pady=(0, 5))
network_entry = tk.Entry(main_frame, textvariable=self.network_path_var, width=50)
network_entry.grid(row=0, column=1, sticky="ew", padx=(0, 5))
tk.Button(main_frame, text="浏览...", command=self.browse_network).grid(row=0, column=2)
# 本地路径选择
tk.Label(main_frame, text="本地路径:").grid(row=1, column=0, sticky="w", pady=(10, 5))
local_entry = tk.Entry(main_frame, textvariable=self.local_path_var, width=50)
local_entry.grid(row=1, column=1, sticky="ew", padx=(0, 5))
tk.Button(main_frame, text="浏览...", command=self.browse_local).grid(row=1, column=2)
# 状态显示
status_frame = tk.Frame(main_frame)
status_frame.grid(row=2, column=0, columnspan=3, pady=(20, 0), sticky="ew")
tk.Label(status_frame, text="状态:").pack(side=tk.LEFT)
tk.Label(status_frame, textvariable=self.status_var, fg="blue").pack(side=tk.LEFT, padx=5)
# 控制按钮
btn_frame = tk.Frame(main_frame)
btn_frame.grid(row=3, column=0, columnspan=3, pady=(30, 10))
self.start_btn = tk.Button(btn_frame, text="启动监控", command=self.toggle_monitoring,
width=15, bg="#4CAF50", fg="white")
self.start_btn.pack(side=tk.LEFT, padx=10)
tk.Button(btn_frame, text="手动复制", command=self.manual_copy,
width=15, bg="#2196F3", fg="white").pack(side=tk.LEFT, padx=10)
tk.Button(btn_frame, text="退出", command=self.root.quit,
width=15, bg="#F44336", fg="white").pack(side=tk.LEFT, padx=10)
# 日志区域
tk.Label(main_frame, text="操作日志:").grid(row=4, column=0, sticky="w", pady=(20, 5))
self.log_text = tk.Text(main_frame, height=8, state=tk.DISABLED)
self.log_text.grid(row=5, column=0, columnspan=3, sticky="nsew")
# 配置网格权重
main_frame.columnconfigure(1, weight=1)
main_frame.rowconfigure(5, weight=1)
# 重定向标准输出到日志区域
self.redirect_stdout()
def redirect_stdout(self):
"""重定向标准输出到文本区域"""
class StdoutRedirector:
def __init__(self, text_widget):
self.text_widget = text_widget
def write(self, string):
self.text_widget.configure(state=tk.NORMAL)
self.text_widget.insert(tk.END, string)
self.text_widget.see(tk.END)
self.text_widget.configure(state=tk.DISABLED)
import sys
sys.stdout = StdoutRedirector(self.log_text)
def browse_network(self):
"""浏览并选择网络路径"""
path = filedialog.askdirectory(title="选择网络路径")
if path:
self.network_path_var.set(path)
def browse_local(self):
"""浏览并选择本地路径"""
path = filedialog.askdirectory(title="选择本地保存路径")
if path:
self.local_path_var.set(path)
def toggle_monitoring(self):
"""切换监控状态"""
if not self.monitoring:
self.start_monitoring()
else:
self.stop_monitoring()
def start_monitoring(self):
"""启动文件监控"""
network_path = self.network_path_var.get()
local_path = self.local_path_var.get()
if not network_path or not local_path:
messagebox.showerror("错误", "请先设置网络路径和本地路径")
return
if not os.path.exists(network_path):
messagebox.showerror("错误", f"网络路径不存在: {network_path}")
return
try:
# 创建监控器
event_handler = FileMonitorHandler(
network_path, local_path, self.status_var
)
self.observer = Observer()
self.observer.schedule(event_handler, network_path, recursive=True)
self.observer.start()
self.monitoring = True
self.start_btn.config(text="停止监控", bg="#F44336")
self.status_var.set(f"正在监控: {network_path}")
# 初始复制所有文件
threading.Thread(target=self.initial_copy, daemon=True).start()
print(f"✅ 已启动监控: {network_path}")
except Exception as e:
messagebox.showerror("启动错误", str(e))
def stop_monitoring(self):
"""停止文件监控"""
if self.observer:
self.observer.stop()
self.observer.join()
self.monitoring = False
self.start_btn.config(text="启动监控", bg="#4CAF50")
self.status_var.set("监控已停止")
print("⏹️ 监控已停止")
def initial_copy(self):
"""初始复制所有文件到本地"""
network_path = self.network_path_var.get()
local_path = self.local_path_var.get()
try:
self.status_var.set("正在扫描文件...")
print(f"🔍 正在扫描: {network_path}")
# 复制所有文件
for root, dirs, files in os.walk(network_path):
for file in files:
src_file = os.path.join(root, file)
dst_file = os.path.join(local_path, os.path.relpath(src_file, network_path))
# 创建目录结构
os.makedirs(os.path.dirname(dst_file), exist_ok=True)
# 复制文件(如果不存在或不同)
if not os.path.exists(dst_file) or \
os.stat(src_file).st_mtime > os.stat(dst_file).st_mtime:
shutil.copy2(src_file, dst_file)
print(f"✅ 已复制: {file}")
self.status_var.set(f"初始复制完成: {len(os.listdir(network_path))} 个文件")
print(f"✅ 初始复制完成")
except Exception as e:
self.status_var.set(f"复制错误: {str(e)}")
print(f"❌ 复制错误: {str(e)}")
def manual_copy(self):
"""手动复制所有文件"""
if self.monitoring:
messagebox.showinfo("提示", "监控已运行,将自动复制新增文件")
return
threading.Thread(target=self.initial_copy, daemon=True).start()
if __name__ == "__main__":
root = tk.Tk()
app = FileMonitorApp(root)
root.protocol("WM_DELETE_WINDOW", lambda: (app.stop_monitoring(), root.quit()))
root.mainloop()
```
### 功能说明
1. **GUI界面功能**:
- 网络路径和本地路径输入框
- 路径浏览按钮(支持网络路径选择)
- 启动/停止监控按钮
- 手动复制按钮
- 状态显示区域
- 操作日志区域
2. **文件监控功能**:
- 使用`watchdog`库实时监控网络路径变化[^1]
- 自动检测新增文件和修改的文件
- 只复制新增或修改过的文件
- 初始运行时自动复制所有文件
3. **路径处理优化**:
- 支持长路径处理(避免Windows路径长度限制)[^1]
- 自动创建目标目录结构
- 递归复制子目录文件
4. **多线程处理**:
- 后台线程执行文件监控
- 后台线程执行文件复制
- 避免阻塞GUI主线程
### 使用方法
1. **安装依赖**:
```bash
pip install watchdog
```
2. **运行程序**:
```python
python file_monitor.py
```
3. **操作步骤**:
1. 设置网络路径(源路径)
2. 设置本地保存路径
3. 点击"启动监控"开始自动同步
4. 使用"手动复制"按钮进行一次性复制
5. 在状态栏和日志区域查看操作状态
### 技术要点
1. **文件变化检测**:
- 使用`watchdog`的`FileSystemEventHandler`类监控文件创建和修改事件
- 通过`on_created`和`on_modified`方法捕获文件变化
2. **路径处理**:
- 使用`os.path.relpath`保持源目录结构
- `os.makedirs(..., exist_ok=True)`确保目录存在
3. **线程安全**:
- 使用`threading`模块创建后台线程
- `daemon=True`确保程序退出时线程终止
4. **GUI优化**:
- 重定向标准输出到日志区域
- 状态变量实时更新界面
- 响应式布局设计
> 注意:访问网络路径可能需要适当的权限,确保程序运行账户有权限访问网络共享[^2]。