Python项目73:自动化文件备份系统1.0(tkinter)

发布于:2025-05-10 ⋅ 阅读:(15) ⋅ 点赞:(0)

主要功能说明:
1.界面组件:源文件夹和目标文件夹选择(带浏览按钮),备份间隔时间设置(分钟),立即备份按钮,自动备份切换按钮,状态栏显示备份状态。
2.进度条显示:使用ttk.Progressbar实现精确进度显示,根据实际文件数量更新进度,实时显示已复制文件数量。
3.文件过滤功能:包含:*.docx, *.xlsx,排除:temp, ~$,支持包含/排除模式(支持通配符)。
4.日志记录系统:自动生成backup_log.txt,记录每次备份的详细信息,支持查看日志窗口(带滚动条)。
5.增量备份选项:通过MD5哈希比较文件内容,仅复制修改过的文件,可切换完整/增量备份模式。

6.使用说明:设置源目录和目标目录,配置过滤规则(可选),选择备份模式(完整/增量),设置自动备份间隔,通过按钮执行立即备份或自动备份,可随时查看备份日志。使用独立线程进行自动备份,避免界面卡顿,自动生成带时间戳的备份目录,实时状态反馈,错误处理机制。

7.注意事项:需要确保目标驱动器有足够空间,长时间运行的自动备份建议在后台运行,第一次使用时建议先进行手动备份测试,增量备份基于文件内容比对,大文件可能会影响性能,过滤规则支持标准shell通配符语法,日志文件保存在程序运行目录,进度条精度取决于文件数量而非大小。
在这里插入图片描述


# -*- coding: utf-8 -*-
# @Author : 小红牛
# 微信公众号:WdPython
import tkinter as tk
from tkinter import filedialog, messagebox, ttk
import shutil
import os
from datetime import datetime
import threading
import time
import fnmatch
import hashlib


class BackupApp:
    def __init__(self, root):
        self.root = root
        self.root.title("高级自动备份工具")
        self.root.geometry("650x450")

        # 初始化变量
        self.is_auto_backup_running = False
        self.total_files = 0
        self.copied_files = 0
        self.backup_mode = tk.StringVar(value="full")  # 备份模式

        # 创建界面组件
        self.create_widgets()
        self.setup_logging()

    def setup_logging(self):
        """初始化日志系统"""
        self.log_path = os.path.join(os.getcwd(), "backup_log.txt")
        if not os.path.exists(self.log_path):
            with open(self.log_path, "w") as f:
                f.write("备份日志记录\n\n")

    def create_widgets(self):
        # 创建主容器
        main_frame = ttk.Frame(self.root)
        main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)

        # 源文件夹选择
        source_frame = ttk.LabelFrame(main_frame, text="源目录设置")
        source_frame.grid(row=0, column=0, columnspan=3, sticky="we", pady=5)

        self.source_entry = ttk.Entry(source_frame, width=50)
        self.source_entry.grid(row=0, column=0, padx=5, pady=5)

        ttk.Button(source_frame, text="浏览...", command=self.select_source).grid(row=0, column=1, padx=5)

        # 目标文件夹选择
        target_frame = ttk.LabelFrame(main_frame, text="目标目录设置")
        target_frame.grid(row=1, column=0, columnspan=3, sticky="we", pady=5)

        self.target_entry = ttk.Entry(target_frame, width=50)
        self.target_entry.grid(row=0, column=0, padx=5, pady=5)

        ttk.Button(target_frame, text="浏览...", command=self.select_target).grid(row=0, column=1, padx=5)

        # 过滤设置
        filter_frame = ttk.LabelFrame(main_frame, text="文件过滤设置")
        filter_frame.grid(row=2, column=0, sticky="we", pady=5, padx=5)

        ttk.Label(filter_frame, text="包含模式(逗号分隔):").grid(row=0, column=0)
        self.include_pattern = ttk.Entry(filter_frame)
        self.include_pattern.grid(row=0, column=1, padx=5)

        ttk.Label(filter_frame, text="排除模式(逗号分隔):").grid(row=1, column=0)
        self.exclude_pattern = ttk.Entry(filter_frame)
        self.exclude_pattern.grid(row=1, column=1, padx=5)

        # 备份设置
        setting_frame = ttk.LabelFrame(main_frame, text="备份设置")
        setting_frame.grid(row=2, column=1, sticky="we", pady=5, padx=5)

        ttk.Label(setting_frame, text="间隔(分钟):").grid(row=0, column=0)
        self.interval_entry = ttk.Entry(setting_frame, width=8)
        self.interval_entry.grid(row=0, column=1, padx=5)
        self.interval_entry.insert(0, "60")

        ttk.Radiobutton(setting_frame, text="完整备份", variable=self.backup_mode, value="full").grid(row=1, column=0)
        ttk.Radiobutton(setting_frame, text="增量备份", variable=self.backup_mode, value="incremental").grid(row=1,
                                                                                                         column=1)

        # 进度条
        self.progress = ttk.Progressbar(main_frame, orient=tk.HORIZONTAL, mode='determinate')
        self.progress.grid(row=3, column=0, columnspan=3, sticky="we", pady=10)

        # 按钮区域
        btn_frame = ttk.Frame(main_frame)
        btn_frame.grid(row=4, column=0, columnspan=3, pady=10)

        ttk.Button(btn_frame, text="立即备份", command=self.manual_backup).pack(side=tk.LEFT, padx=5)
        self.auto_btn = ttk.Button(btn_frame, text="开始自动备份", command=self.toggle_auto_backup)
        self.auto_btn.pack(side=tk.LEFT, padx=5)
        ttk.Button(btn_frame, text="查看日志", command=self.show_logs).pack(side=tk.LEFT, padx=5)

        # 状态栏
        self.status_label = ttk.Label(main_frame, text="状态: 就绪", relief=tk.SUNKEN)
        self.status_label.grid(row=5, column=0, columnspan=3, sticky="we")

    def select_source(self):
        path = filedialog.askdirectory()
        if path:
            self.source_entry.delete(0, tk.END)
            self.source_entry.insert(0, path)

    def select_target(self):
        path = filedialog.askdirectory()
        if path:
            self.target_entry.delete(0, tk.END)
            self.target_entry.insert(0, path)

    def manual_backup(self):
        threading.Thread(target=self.perform_backup, daemon=True).start()

    def should_include(self, file_path):
        """文件过滤逻辑"""
        include = self.include_pattern.get().strip()
        exclude = self.exclude_pattern.get().strip()
        filename = os.path.basename(file_path)

        # 包含规则
        if include:
            patterns = [p.strip() for p in include.split(",")]
            if not any(fnmatch.fnmatch(filename, p) for p in patterns):
                return False

        # 排除规则
        if exclude:
            patterns = [p.strip() for p in exclude.split(",")]
            if any(fnmatch.fnmatch(filename, p) for p in patterns):
                return False

        return True

    def get_file_hash(self, file_path):
        """计算文件哈希值(用于增量备份)"""
        hasher = hashlib.md5()
        with open(file_path, 'rb') as f:
            while chunk := f.read(8192):
                hasher.update(chunk)
        return hasher.hexdigest()

    def perform_backup(self):
        """执行备份的核心方法"""
        try:
            source = self.source_entry.get()
            target = self.target_entry.get()

            if not source or not target:
                messagebox.showerror("错误", "请先选择源文件夹和目标文件夹")
                return

            timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
            backup_folder = os.path.join(target, f"backup_{timestamp}")
            os.makedirs(backup_folder, exist_ok=True)

            # 获取文件列表并过滤
            all_files = []
            for root, dirs, files in os.walk(source):
                for file in files:
                    full_path = os.path.join(root, file)
                    if self.should_include(full_path):
                        all_files.append(full_path)

            self.total_files = len(all_files)
            self.copied_files = 0
            self.progress['maximum'] = self.total_files
            self.progress['value'] = 0

            # 记录日志
            log_entry = {
                'timestamp': datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
                'source': source,
                'target': backup_folder,
                'mode': self.backup_mode.get(),
                'status': 'success',
                'files': 0
            }

            # 执行备份
            for file_path in all_files:
                if not self.is_auto_backup_running and self.backup_mode.get() == "incremental":
                    break  # 允许取消增量备份

                rel_path = os.path.relpath(file_path, source)
                dest_path = os.path.join(backup_folder, rel_path)
                os.makedirs(os.path.dirname(dest_path), exist_ok=True)

                # 增量备份检查
                if self.backup_mode.get() == "incremental":
                    if os.path.exists(dest_path):
                        src_hash = self.get_file_hash(file_path)
                        dst_hash = self.get_file_hash(dest_path)
                        if src_hash == dst_hash:
                            continue

                shutil.copy2(file_path, dest_path)
                self.copied_files += 1
                self.update_progress()

            log_entry['files'] = self.copied_files
            self.log_backup(log_entry)
            self.update_status(f"备份完成:复制 {self.copied_files}/{self.total_files} 个文件")

        except Exception as e:
            self.update_status(f"备份失败:{str(e)}")
            messagebox.showerror("错误", str(e))
            log_entry['status'] = 'failed'
            log_entry['error'] = str(e)
            self.log_backup(log_entry)

    def update_progress(self):
        """更新进度条"""
        self.progress['value'] = self.copied_files
        self.root.update_idletasks()

    def log_backup(self, entry):
        """记录备份日志"""
        with open(self.log_path, "a") as f:
            f.write(f"[{entry['timestamp']}] {entry['mode'].upper()}备份 "
                    f"源目录: {entry['source']}\n目标目录: {entry['target']}\n"
                    f"状态: {entry['status'].upper()} 文件数: {entry['files']}\n")
            if 'error' in entry:
                f.write(f"错误信息: {entry['error']}\n")
            f.write("-" * 50 + "\n")

    def show_logs(self):
        """显示日志窗口"""
        log_win = tk.Toplevel(self.root)
        log_win.title("备份日志")

        text_area = tk.Text(log_win, wrap=tk.WORD, width=80, height=20)
        scroll = ttk.Scrollbar(log_win, command=text_area.yview)
        text_area.configure(yscrollcommand=scroll.set)

        with open(self.log_path, "r") as f:
            text_area.insert(tk.END, f.read())

        text_area.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        scroll.pack(side=tk.RIGHT, fill=tk.Y)

    def toggle_auto_backup(self):
        if not self.is_auto_backup_running:
            try:
                interval = int(self.interval_entry.get()) * 60
                if interval <= 0:
                    raise ValueError
            except ValueError:
                messagebox.showerror("错误", "请输入有效的正整数分钟数")
                return

            self.is_auto_backup_running = True
            self.auto_btn.config(text="停止自动备份")
            self.update_status("自动备份已启动...")

            self.auto_backup_thread = threading.Thread(
                target=self.auto_backup_loop, args=(interval,), daemon=True
            )
            self.auto_backup_thread.start()
        else:
            self.is_auto_backup_running = False
            self.auto_btn.config(text="开始自动备份")
            self.update_status("自动备份已停止")

    def auto_backup_loop(self, interval):
        while self.is_auto_backup_running:
            self.perform_backup()
            for _ in range(interval):
                if not self.is_auto_backup_running:
                    return
                time.sleep(1)

    def update_status(self, message):
        self.status_label.config(text=f"状态: {message}")


if __name__ == "__main__":
    root = tk.Tk()
    app = BackupApp(root)
    root.mainloop()

完毕!!感谢您的收看

----------★★跳转到历史博文集合★★----------
我的零基础Python教程,Python入门篇 进阶篇 视频教程 Py安装py项目 Python模块 Python爬虫 Json Xpath 正则表达式 Selenium Etree CssGui程序开发 Tkinter Pyqt5 列表元组字典数据可视化 matplotlib 词云图 Pyecharts 海龟画图 Pandas Bug处理 电脑小知识office自动化办公 编程工具 NumPy Pygame


网站公告

今日签到

点亮在社区的每一天
去签到