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

主要功能说明:
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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值