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

news2025/5/9 18:09:14

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2371684.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++:扫雷游戏

一.扫雷游戏项目设计 1.文件结构设计 首先我们要先定义三个文件 ①test.c //文件中写游戏的测试逻辑 ②game.c //文件中写游戏中函数的实现等 ③game.h //文件中写游戏需要的数据类型和函数声明等 2.扫雷游戏的主体结构 使⽤控制台实现经典的扫雷游戏 •游戏可以通过菜单…

使用xlwings将excel表中将无规律的文本型数字批量转化成真正的数字

之前我写了一篇文章excel表中将无规律的文本型数字批量转化成真正的数字-CSDN博客 是使用excel自带的操作&#xff0c;相对繁琐。 今天使用xlwings操作&#xff0c;表格如下&#xff08;有真正的数字&#xff0c;也有文本型数字&#xff0c;混在在一起&#xff09;&#xff1…

文件包含 任意文件读取

文件处理漏洞--文件包含 - wizard骑士 - 博客园 1&#xff0c;什么是文件包含 程序开发人员一般会吧重复使用的函数写道单个文件中&#xff0c;需要使用某个函数时直接调用此文件&#xff0c;无需再次编写&#xff0c;文件调用的过程就是文件包含&#xff0c;所以将包含的文件…

缓存套餐-01.Spring Cache介绍和常用注解

一.Spring Cache 要使用直接导入坐标即可。 如何选择底层的缓存实现呢&#xff1f;只要导入对应的缓存坐标即可。如果要使用redis作为缓存实现&#xff0c;那么只需要导入redis的maven坐标。 二.常用注解 Cacheable&#xff1a;不光往缓存中写缓存数据&#xff0c;而且会从缓…

C++类与对象—下:夯实面向对象编程的阶梯

9. 赋值运算符重载 9.1 运算符重载 在 C 里&#xff0c;运算符重载能够让自定义类型的对象像内置类型那样使用运算符&#xff0c;这极大地提升了代码的可读性与可维护性。运算符重载本质上是一种特殊的函数&#xff0c;其函数名是 operator 加上要重载的运算符。 下面是运算…

Linux中安装mysql8,转载及注意事项

一、先前往官网下载mysql8 下载地址&#xff1a; https://dev.mysql.com/downloads/选择Linux 二、删除Linux中的mysql&#xff08;如果有的话&#xff09;&#xff0c;上传安装包 1、先查看mysql是否存在&#xff0c;命令如下&#xff1a; rpm -qa|grep -i mysql如果使用这…

SpringBoot的汽车商城后台管理系统源码开发实现

概述 汽车商城后台管理系统专为汽车4S店和经销商设计&#xff0c;提供全面的汽车管理系统解决方案。 主要内容 1. 核心功能模块 系统提供以下主要功能&#xff1a; ​​销售管理​​&#xff1a;记录销售信息&#xff0c;跟踪交易进度​​客户管理​​&#xff1a;维护客户…

DeepSeek实战--手搓实现Agent

1.背景 要学习AI agent&#xff0c;只会用agent 框架&#xff0c;还不够&#xff0c;一旦框架出现问题&#xff0c;没法快速的排查出问题。 学习就应该“知其然&#xff0c;更应该知其所以然” &#xff0c;今天我们就用编码的方式实现一个简单的agent 。我们模拟一套AI学生评…

线性代数——行列式⭐

目录 一、行列式的定义⭐ 1-1、三阶行列式练习 1-2、下面介绍下三角行列式、上三角行列式、对角行列式 ​编辑 二、行列式的性质 2-1、性质1&#xff0c;2&#xff0c;3&#xff0c;4&#xff0c;5&#xff0c;6 ​编辑 2-2、性质7 2- 3、拉普拉斯定理、克莱姆法则 三…

iPhone手机连接WiFi异常解决方法

iPhone手机连接WiFi异常解决方法 一、问题现象二、iPhone连不上可能的原因三、基础排查与快速修复第一步:重启大法第二步:忽略网络,重新认证第三步:关闭“私有无线局域网地址”第四步:修改DNS服务器第五步:还原网络设置四、路由器端排查及设置关闭MAC地址过滤或添加到白名…

学习设计模式《八》——原型模式

一、基础概念 原型模式的本质是【克隆生成对象】&#xff1b; 原型模式的定义&#xff1a;用原型实例指定创建对象的种类&#xff0c;并通过拷贝这些原型创建新的对象 。 原型模式的功能&#xff1a; 1、通过克隆来创建新的对象实例&#xff1b; 2、为克隆出来的新对象实例复制…

疗愈服务预约小程序源码介绍

基于ThinkPHP、FastAdmin和UniApp开发的疗愈服务预约小程序源码&#xff0c;这款小程序在功能设计和用户体验上都表现出色&#xff0c;为疗愈行业提供了一种全新的服务模式。 该小程序源码采用了ThinkPHP作为后端框架&#xff0c;保证了系统的稳定性和高效性。同时&#xff0c…

【随笔】Google学术:but your computer or network may be sending automated queries.

文章目录 一、问题复述二、问题原因三、解决 前提&#xff1a;你的xxx是自己做的&#xff0c;你自己可以管理&#xff0c;而不是用的那些劣质✈场。 一、问题复述 &#x1f7e2;如下图所示&#xff1a;可以打开谷歌学术&#xff0c;但是一搜索就是这个界面。 二、问题原因 …

长事务:数据库中的“隐形炸弹“——金仓数据库运维避坑指南

引言&#xff1a;凌晨三点的告警 "张工&#xff01;生产库又告警了&#xff01;"凌晨三点的电话铃声总是格外刺耳。运维团队发现数据库频繁进入单用户模式&#xff0c;排查发现某核心表的年龄值&#xff08;Age&#xff09;已突破20亿大关。经过一夜奋战&#xff0c…

ubuntu nobel + qt5.15.2 设置qss语法识别正确

问题展示 解决步骤 首选项里面的高亮怎么编辑选择都没用。如果已经有generic-highlighter和css.xml&#xff0c;直接修改css.xml文件最直接&#xff01; 在generic-highlighter目录下找到css.xml文件&#xff0c;位置是&#xff1a;/opt/Qt/Tools/QtCreator/share/qtcreator/…

Unity-Socket通信实例详解

今天我们来讲解socket通信。 首先我们需要知道什么是socket通信&#xff1a; Socket本质上就是一个个进程之间网络通信的基础&#xff0c;每一个Socket由IP端口组成&#xff0c;熟悉计网的同学应该知道IP主要是应用于IP协议而端口主要应用于TCP协议&#xff0c;这也证明了Sock…

MATLAB仿真定点数转浮点数(对比VIVADO定点转浮点)

MATLAB仿真定点数转浮点数 定点数可设置位宽&#xff0c;小数位宽&#xff1b;浮点数是单精度浮点数 对比VIVADO定点转浮点 目录 前言 一、定点数 二、浮点数 三、定点数转浮点数 四、函数代码 总结 前言 在FPGA上实现算法时&#xff0c;相比MATLAB实现往往需要更长的开发…

【计算机网络】Cookie、Session、Token之间有什么区别?

大家在日常使用浏览器时可能会遇到&#xff1a;是否清理Cookie&#xff1f;这个问题。 那么什么是Cookie呢&#xff1f;与此相关的还有Session、Token这些。这两个又是什么呢&#xff1f; 本文将对这三个进行讲解区分&#xff0c;如果对小伙伴有帮助的话&#xff0c;也请点赞、…

SpringCloud服务拆分:Nacos服务注册中心 + LoadBalancer服务负载均衡使用

SpringCloud中Nacos服务注册中心 LoadBalancer服务负载均衡使用 前言Nacos工作流程nacos安装docker安装window安装 运行nacos微服务集成nacos高级特性1.服务集群配置方法效果图模拟服务实例宕机 2.权重配置3.环境隔离 如何启动集群节点本地启动多个节点方法 LoadBalancer集成L…

Apache Doris 使用指南:从入门到生产实践

目录 一、Doris 核心概念 1.1 架构组成 1.2 数据模型 二、Doris 部署方式 2.1 单机部署&#xff08;测试环境&#xff09; 2.2 集群部署&#xff08;生产环境&#xff09; 三、数据操作指南 3.1 数据库与表管理 3.2 数据导入方式 3.2.1 批量导入 3.2.2 实时导入 3.…