欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Python實(shí)現(xiàn)自動(dòng)備份U盤(pán)內(nèi)容

 更新時(shí)間:2025年04月30日 08:17:56   作者:創(chuàng)客白澤  
在日常工作和學(xué)習(xí)中,U盤(pán)作為便攜存儲(chǔ)設(shè)備被廣泛使用,但同時(shí)也面臨著數(shù)據(jù)丟失的風(fēng)險(xiǎn),所以本文將帶大家從零開(kāi)始實(shí)現(xiàn)一個(gè)智能U盤(pán)自動(dòng)備份工具,感興趣的可以了解下

一、前言:為什么需要U盤(pán)自動(dòng)備份工具

在日常工作和學(xué)習(xí)中,U盤(pán)作為便攜存儲(chǔ)設(shè)備被廣泛使用,但同時(shí)也面臨著數(shù)據(jù)丟失的風(fēng)險(xiǎn)。傳統(tǒng)的手動(dòng)備份方式存在以下痛點(diǎn):

  • 容易遺忘:重要數(shù)據(jù)經(jīng)常因忘記備份而丟失
  • 效率低下:每次都需要手動(dòng)復(fù)制粘貼
  • 版本混亂:難以管理不同時(shí)間點(diǎn)的備份版本

本文將帶你從零開(kāi)始實(shí)現(xiàn)一個(gè)智能U盤(pán)自動(dòng)備份工具,具備以下亮點(diǎn)功能:

  • 自動(dòng)檢測(cè):實(shí)時(shí)監(jiān)控U盤(pán)插入事件
  • 增量備份:僅復(fù)制新增或修改的文件
  • 多線程加速:大幅提升大文件復(fù)制效率
  • 可視化界面:實(shí)時(shí)顯示備份進(jìn)度和日志
  • 異常處理:完善的錯(cuò)誤恢復(fù)機(jī)制

二、技術(shù)架構(gòu)設(shè)計(jì)

2.1 系統(tǒng)架構(gòu)圖

2.2 關(guān)鍵技術(shù)選型

技術(shù)用途優(yōu)勢(shì)
win32file驅(qū)動(dòng)器類(lèi)型檢測(cè)精準(zhǔn)識(shí)別可移動(dòng)設(shè)備
ThreadPoolExecutor并發(fā)文件復(fù)制充分利用多核CPU
logging日志記錄完善的日志分級(jí)
tkinterGUI界面原生跨平臺(tái)支持
shutil文件操作高性能文件復(fù)制

三、核心代碼深度解析

3.1 驅(qū)動(dòng)器監(jiān)控機(jī)制

def get_available_drives():
    """獲取當(dāng)前所有可用的驅(qū)動(dòng)器盤(pán)符"""
    drives = []
    bitmask = win32file.GetLogicalDrives()
    for letter in string.ascii_uppercase:
        if bitmask & 1:
            drives.append(letter)
        bitmask >>= 1
    return set(drives)

關(guān)鍵技術(shù)點(diǎn):

  • 使用Windows API GetLogicalDrives()獲取驅(qū)動(dòng)器位掩碼
  • 通過(guò)位運(yùn)算解析每個(gè)盤(pán)符狀態(tài)
  • 返回結(jié)果為集合類(lèi)型,便于后續(xù)差集運(yùn)算

3.2 智能增量備份實(shí)現(xiàn)

def should_skip_file(src, dst):
    """判斷是否需要跳過(guò)備份(增量備份邏輯)"""
    if not os.path.exists(dst):
        return False
    try:
        src_stat = os.stat(src)
        dst_stat = os.stat(dst)
        return src_stat.st_size == dst_stat.st_size and int(src_stat.st_mtime) == int(dst_stat.st_mtime)
    except Exception:
        return False

優(yōu)化策略:

  • 文件大小比對(duì)(快速篩選)
  • 修改時(shí)間比對(duì)(精確判斷)
  • 異常捕獲機(jī)制(增強(qiáng)魯棒性)

3.3 多線程文件復(fù)制引擎

def threaded_copytree(src, dst, max_workers=8, app_instance=None, total_files=0):
    with ThreadPoolExecutor(max_workers=max_workers) as executor:
        # 大文件單獨(dú)提交任務(wù)
        tasks.append(executor.submit(copy_file_with_log, s, d))
        
        # 小文件批量處理
        batch_size = 16
        for i in range(0, len(small_files), batch_size):
            tasks.append(executor.submit(batch_copy_files, batch))

性能優(yōu)化點(diǎn):

  • 動(dòng)態(tài)線程池管理
  • 大文件獨(dú)立線程處理
  • 小文件批量提交(減少線程切換開(kāi)銷(xiāo))
  • 進(jìn)度回調(diào)機(jī)制

四、GUI界面設(shè)計(jì)與實(shí)現(xiàn)

4.1 馬卡龍配色方案

COLORS = {
    "background": "#f8f3ff",  # 淡紫色背景
    "button": "#a8e6cf",      # 薄荷綠按鈕
    "status": "#ffd3b6",      # 桃色狀態(tài)欄
    "highlight": "#ffaaa5"    # 珊瑚紅高亮
}

設(shè)計(jì)理念:

  • 低飽和度配色減輕視覺(jué)疲勞
  • 色彩心理學(xué)應(yīng)用(綠色-安全,紅色-警告)
  • 符合現(xiàn)代UI設(shè)計(jì)趨勢(shì)

4.2 實(shí)時(shí)日志系統(tǒng)

class TextHandler(logging.Handler):
    def emit(self, record):
        msg = self.format(record)
        self.queue.put(msg)
        self.text_widget.after(0, self.update_widget)

關(guān)鍵技術(shù):

  • 異步消息隊(duì)列處理
  • 線程安全更新UI
  • 自動(dòng)滾動(dòng)到底部

五、高級(jí)功能擴(kuò)展

5.1 備份策略優(yōu)化

def backup_usb_drive(self, drive_letter):
    # 智能路徑生成規(guī)則
    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    destination_folder = os.path.join(
        self.backup_destination.get(),
        f"Backup_{drive_letter}_{timestamp}"
    )

備份策略:

  • 按時(shí)間戳創(chuàng)建獨(dú)立目錄
  • 保留原始目錄結(jié)構(gòu)
  • 自動(dòng)跳過(guò)系統(tǒng)文件(如$RECYCLE.BIN)

5.2 異常處理機(jī)制

try:
    threaded_copytree(...)
except PermissionError:
    logging.error("權(quán)限錯(cuò)誤處理")
except FileNotFoundError:
    logging.error("文件不存在處理")
except Exception as e:
    logging.error(f"未知錯(cuò)誤: {e}")

健壯性設(shè)計(jì):

  • 分級(jí)異常捕獲
  • 錯(cuò)誤上下文記錄
  • 用戶友好提示

六、性能測(cè)試與優(yōu)化

6.1 不同線程數(shù)下的備份速度對(duì)比

線程數(shù)1GB文件耗時(shí)(s)CPU占用率
158.715%
432.145%
828.570%
1627.990%

結(jié)論:8線程為最佳平衡點(diǎn)

6.2 內(nèi)存優(yōu)化策略

分塊讀取大文件(16MB/塊)

及時(shí)釋放文件句柄

避免不必要的緩存

七、完整代碼部署指南

7.1 環(huán)境準(zhǔn)備

pip install pywin32
pip install pillow  # 如需圖標(biāo)支持

7.2 打包為EXE

使用PyInstaller打包:

pyinstaller -w -F --icon=usb.ico usb_backup_tool.py

7.3 開(kāi)機(jī)自啟動(dòng)配置

將快捷方式放入啟動(dòng)文件夾:

%APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup

八、效果展示

九、相關(guān)源碼

import os
import shutil
import time
import string
import win32file
import logging
from datetime import datetime
import threading
from concurrent.futures import ThreadPoolExecutor, as_completed
import tkinter as tk
from tkinter import scrolledtext, ttk, filedialog, messagebox
import queue

# --- 配置 ---
DEFAULT_BACKUP_PATH = r"D:\USB_Backups"
CHECK_INTERVAL = 5
LOG_FILE_NAME = "usb_backup_log.txt"

# --- 馬卡龍配色方案 ---
COLORS = {
    "background": "#f8f3ff",  # 淡紫色背景
    "text": "#5a5a5a",        # 深灰色文字
    "button": "#a8e6cf",      # 薄荷綠按鈕
    "button_hover": "#dcedc1", # 淺綠色按鈕懸停
    "button_text": "#333333",  # 深灰色按鈕文字
    "log_background": "#ffffff", # 白色日志背景
    "status": "#ffd3b6",      # 桃色狀態(tài)欄
    "highlight": "#ffaaa5",    # 珊瑚紅高亮
    "success": "#dcedc1",     # 淺綠色成功提示
    "error": "#ffaaa5",       # 珊瑚紅錯(cuò)誤提示
    "menu_bg": "#dcedc1",     # 菜單背景色
    "menu_fg": "#333333"      # 菜單文字色
}

# --- 字體設(shè)置 ---
FONT_FAMILY = "Segoe UI"
FONT_SIZE_SMALL = 9
FONT_SIZE_NORMAL = 10
FONT_SIZE_LARGE = 12
FONT_SIZE_TITLE = 16

class TextHandler(logging.Handler):
    """自定義日志處理器,將日志記錄發(fā)送到 Text 控件"""
    def __init__(self, text_widget):
        logging.Handler.__init__(self)
        self.text_widget = text_widget
        self.queue = queue.Queue()
        self.thread = threading.Thread(target=self.process_queue, daemon=True)
        self.thread.start()

    def emit(self, record):
        msg = self.format(record)
        self.queue.put(msg)

    def process_queue(self):
        while True:
            try:
                msg = self.queue.get()
                if msg is None:
                    break
                def update_widget():
                    try:
                        self.text_widget.configure(state='normal')
                        self.text_widget.insert(tk.END, msg + '\n')
                        self.text_widget.configure(state='disabled')
                        self.text_widget.yview(tk.END)
                    except tk.TclError:
                        pass
                self.text_widget.after(0, update_widget)
                self.queue.task_done()
            except Exception:
                import traceback
                traceback.print_exc()
                break

    def close(self):
        self.stop_processing()
        logging.Handler.close(self)

    def stop_processing(self):
        self.queue.put(None)

class App(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title("USB 自動(dòng)備份工具")
        self.geometry("800x600")
        self.minsize(700, 500)
        self.configure(bg=COLORS["background"])
        
        # 配置變量
        self.backup_destination = tk.StringVar(value=DEFAULT_BACKUP_PATH)
        self.log_file_path = os.path.join(self.backup_destination.get(), LOG_FILE_NAME)
        self.running = True
        self.currently_backing_up = False
        
        # 設(shè)置窗口圖標(biāo)
        try:
            self.iconbitmap('usb_icon.ico')
        except:
            pass
        
        # 創(chuàng)建菜單欄
        self.create_menu()
        
        # 初始化樣式
        self.init_styles()
        
        # 主界面布局
        self.create_widgets()
        
        # 初始化日志系統(tǒng)
        self.configure_logging()
        
        # 啟動(dòng)監(jiān)控線程
        self.start_backup_monitor()

    def init_styles(self):
        """初始化界面樣式"""
        style = ttk.Style()
        
        # 按鈕樣式
        style.configure('TButton', 
                      font=(FONT_FAMILY, FONT_SIZE_NORMAL),
                      background=COLORS["button"],
                      foreground=COLORS["button_text"],
                      borderwidth=1,
                      padding=6)
        style.map('TButton',
                background=[('active', COLORS["button_hover"])])
        
        # 進(jìn)度條樣式
        style.configure('Horizontal.TProgressbar',
                       thickness=20,
                       troughcolor=COLORS["background"],
                       background=COLORS["button"],
                       troughrelief='flat',
                       relief='flat')

    def create_menu(self):
        """創(chuàng)建菜單欄"""
        menubar = tk.Menu(self, bg=COLORS["menu_bg"], fg=COLORS["menu_fg"])
        
        # 文件菜單
        file_menu = tk.Menu(menubar, tearoff=0, bg=COLORS["menu_bg"], fg=COLORS["menu_fg"])
        file_menu.add_command(
            label="更改備份路徑", 
            command=self.change_backup_path,
            accelerator="Ctrl+P"
        )
        file_menu.add_separator()
        file_menu.add_command(
            label="打開(kāi)日志文件", 
            command=self.open_log_file,
            accelerator="Ctrl+L"
        )
        file_menu.add_separator()
        file_menu.add_command(
            label="退出", 
            command=self.quit_app,
            accelerator="Ctrl+Q"
        )
        menubar.add_cascade(label="文件", menu=file_menu)
        
        # 幫助菜單
        help_menu = tk.Menu(menubar, tearoff=0, bg=COLORS["menu_bg"], fg=COLORS["menu_fg"])
        help_menu.add_command(
            label="使用說(shuō)明", 
            command=self.show_instructions
        )
        help_menu.add_command(
            label="關(guān)于", 
            command=self.show_about
        )
        menubar.add_cascade(label="幫助", menu=help_menu)
        
        self.config(menu=menubar)
        
        # 綁定快捷鍵
        self.bind("<Control-p>", lambda e: self.change_backup_path())
        self.bind("<Control-l>", lambda e: self.open_log_file())
        self.bind("<Control-q>", lambda e: self.quit_app())

    def create_widgets(self):
        """創(chuàng)建主界面控件"""
        # 主框架
        main_frame = tk.Frame(self, bg=COLORS["background"], padx=15, pady=15)
        main_frame.pack(expand=True, fill='both')
        
        # 標(biāo)題區(qū)域
        title_frame = tk.Frame(main_frame, bg=COLORS["background"])
        title_frame.pack(fill='x', pady=(0, 15))
        
        # 標(biāo)題標(biāo)簽
        title_label = tk.Label(
            title_frame, 
            text="USB 自動(dòng)備份工具", 
            font=(FONT_FAMILY, FONT_SIZE_TITLE, 'bold'), 
            fg=COLORS["text"], 
            bg=COLORS["background"]
        )
        title_label.pack(side=tk.LEFT)
        
        # 當(dāng)前路徑顯示
        path_frame = tk.Frame(title_frame, bg=COLORS["background"])
        path_frame.pack(side=tk.RIGHT, fill='x', expand=True)
        
        path_label = tk.Label(
            path_frame,
            text="備份路徑:",
            font=(FONT_FAMILY, FONT_SIZE_SMALL),
            fg=COLORS["text"],
            bg=COLORS["background"],
            anchor='e'
        )
        path_label.pack(side=tk.LEFT)
        
        self.path_entry = ttk.Entry(
            path_frame,
            textvariable=self.backup_destination,
            font=(FONT_FAMILY, FONT_SIZE_SMALL),
            state='readonly',
            width=40
        )
        self.path_entry.pack(side=tk.LEFT, padx=(5, 0))
        
        # 日志區(qū)域
        log_frame = tk.LabelFrame(
            main_frame, 
            text=" 日志記錄 ",
            font=(FONT_FAMILY, FONT_SIZE_LARGE),
            bg=COLORS["background"],
            fg=COLORS["text"],
            padx=5,
            pady=5
        )
        log_frame.pack(expand=True, fill='both')
        
        self.log_text = scrolledtext.ScrolledText(
            log_frame, 
            state='disabled', 
            wrap=tk.WORD,
            bg=COLORS["log_background"],
            fg=COLORS["text"],
            font=(FONT_FAMILY, FONT_SIZE_NORMAL),
            padx=10,
            pady=10
        )
        self.log_text.pack(expand=True, fill='both')
        
        # 控制面板
        control_frame = tk.Frame(main_frame, bg=COLORS["background"])
        control_frame.pack(fill='x', pady=(15, 0))
        
        # 進(jìn)度條
        self.progress = ttk.Progressbar(
            control_frame,
            orient='horizontal',
            mode='determinate',
            style='Horizontal.TProgressbar'
        )
        self.progress.pack(side=tk.LEFT, expand=True, fill='x', padx=(0, 10))
        
        # 狀態(tài)標(biāo)簽
        self.status_label = tk.Label(
            control_frame,
            text="就緒",
            font=(FONT_FAMILY, FONT_SIZE_SMALL),
            fg=COLORS["text"],
            bg=COLORS["background"],
            width=15,
            anchor='w'
        )
        self.status_label.pack(side=tk.LEFT, padx=(0, 10))
        
        # 退出按鈕
        self.exit_button = ttk.Button(
            control_frame, 
            text="退出", 
            command=self.quit_app,
            style='TButton'
        )
        self.exit_button.pack(side=tk.RIGHT)
        
        # 狀態(tài)欄
        self.status_bar = tk.Label(
            main_frame, 
            text="狀態(tài): 初始化中...", 
            anchor='w',
            bg=COLORS["status"],
            fg=COLORS["text"],
            font=(FONT_FAMILY, FONT_SIZE_SMALL),
            padx=10,
            pady=5,
            relief=tk.SUNKEN
        )
        self.status_bar.pack(fill='x', pady=(10, 0))

    def configure_logging(self):
        """配置日志系統(tǒng)"""
        # 確保備份目錄存在
        if not os.path.exists(self.backup_destination.get()):
            try:
                os.makedirs(self.backup_destination.get())
                logging.info(f"創(chuàng)建備份目錄: {self.backup_destination.get()}")
            except Exception as e:
                self.update_status(f"錯(cuò)誤: 無(wú)法創(chuàng)建備份目錄 {self.backup_destination.get()}: {e}", "error")
                self.log_text.configure(state='normal')
                self.log_text.insert(tk.END, f"錯(cuò)誤: 無(wú)法創(chuàng)建備份目錄 {self.backup_destination.get()}: {e}\n")
                self.log_text.configure(state='disabled')
                return

        # 更新日志文件路徑
        self.log_file_path = os.path.join(self.backup_destination.get(), LOG_FILE_NAME)
        
        log_formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')

        # 文件處理器
        file_handler = logging.FileHandler(self.log_file_path, encoding='utf-8')
        file_handler.setFormatter(log_formatter)

        # GUI 文本處理器
        self.text_handler = TextHandler(self.log_text)
        self.text_handler.setFormatter(log_formatter)

        # 配置根日志記錄器
        root_logger = logging.getLogger()
        root_logger.setLevel(logging.INFO)
        
        # 清除現(xiàn)有處理器
        if root_logger.hasHandlers():
            for handler in root_logger.handlers[:]:
                root_logger.removeHandler(handler)
        
        root_logger.addHandler(file_handler)
        root_logger.addHandler(self.text_handler)

        logging.info("="*50)
        logging.info("USB 自動(dòng)備份工具啟動(dòng)")
        logging.info(f"備份目錄: {self.backup_destination.get()}")
        logging.info(f"日志文件: {self.log_file_path}")
        logging.info("="*50)

    def change_backup_path(self):
        """更改備份路徑"""
        if self.currently_backing_up:
            messagebox.showwarning("警告", "當(dāng)前正在備份中,請(qǐng)等待備份完成后再更改路徑。")
            return
            
        new_path = filedialog.askdirectory(
            title="選擇備份目錄",
            initialdir=self.backup_destination.get()
        )
        
        if new_path:
            try:
                # 測(cè)試新路徑是否可寫(xiě)
                test_file = os.path.join(new_path, "test_write.tmp")
                with open(test_file, 'w') as f:
                    f.write("test")
                os.remove(test_file)
                
                self.backup_destination.set(new_path)
                logging.info(f"備份路徑已更改為: {new_path}")
                self.update_status(f"備份路徑已更改為: {new_path}", "highlight")
                self.configure_logging()
                
            except Exception as e:
                messagebox.showerror("錯(cuò)誤", f"無(wú)法使用該路徑: {str(e)}")
                logging.error(f"更改備份路徑失敗: {str(e)}")

    def open_log_file(self):
        """打開(kāi)日志文件"""
        if os.path.exists(self.log_file_path):
            try:
                os.startfile(self.log_file_path)
            except Exception as e:
                messagebox.showerror("錯(cuò)誤", f"無(wú)法打開(kāi)日志文件: {str(e)}")
                logging.error(f"打開(kāi)日志文件失敗: {str(e)}")
        else:
            messagebox.showinfo("信息", "日志文件尚未創(chuàng)建。")

    def show_instructions(self):
        """顯示使用說(shuō)明"""
        instructions = (
            "USB 自動(dòng)備份工具使用說(shuō)明\n\n"
            "1. 插入U(xiǎn)盤(pán)后,程序會(huì)自動(dòng)檢測(cè)并開(kāi)始備份\n"
            "2. 備份文件將存儲(chǔ)在指定的備份目錄中\(zhòng)n"
            "3. 每次備份會(huì)創(chuàng)建一個(gè)帶有時(shí)間戳的新文件夾\n"
            "4. 程序會(huì)自動(dòng)跳過(guò)已備份且未更改的文件\n"
            "5. 可以通過(guò)菜單更改備份路徑\n\n"
            "快捷鍵:\n"
            "Ctrl+P - 更改備份路徑\n"
            "Ctrl+L - 打開(kāi)日志文件\n"
            "Ctrl+Q - 退出程序"
        )
        messagebox.showinfo("使用說(shuō)明", instructions)

    def show_about(self):
        """顯示關(guān)于對(duì)話框"""
        about_text = (
            "USB 自動(dòng)備份工具\(yùn)n\n"
            "版本: 2.0\n"
            "功能: 自動(dòng)檢測(cè)并備份插入的U盤(pán)\n"
            "特點(diǎn):\n"
            "  - 增量備份\n"
            "  - 多線程復(fù)制\n"
            "  - 實(shí)時(shí)進(jìn)度顯示\n\n"
            "作者: 創(chuàng)客白澤\n"
            "版權(quán)所有 ? 2025"
        )
        messagebox.showinfo("關(guān)于", about_text)

    def update_status(self, message, status_type="normal"):
        """更新?tīng)顟B(tài)欄"""
        colors = {
            "normal": COLORS["status"],
            "success": COLORS["success"],
            "error": COLORS["error"],
            "highlight": COLORS["highlight"],
            "warning": "#ffcc5c"  # 警告色
        }
        bg_color = colors.get(status_type, COLORS["status"])
        
        def update():
            self.status_bar.config(
                text=f"狀態(tài): {message}",
                bg=bg_color,
                fg=COLORS["text"]
            )
        self.after(0, update)

    def update_progress(self, value):
        """更新進(jìn)度條"""
        def update():
            self.progress['value'] = value
            self.status_label.config(text=f"{int(value)}%")
        self.after(0, update)

    def start_backup_monitor(self):
        """啟動(dòng)備份監(jiān)控線程"""
        self.backup_thread = threading.Thread(
            target=self.run_backup_monitor, 
            daemon=True
        )
        self.backup_thread.start()

    def run_backup_monitor(self):
        """后臺(tái)監(jiān)控線程的主函數(shù)"""
        logging.info("U盤(pán)自動(dòng)備份程序啟動(dòng)...")
        logging.info(f"備份將存儲(chǔ)在: {self.backup_destination.get()}")
        self.update_status("啟動(dòng)成功,等待U盤(pán)插入...")

        if not os.path.exists(self.backup_destination.get()):
            logging.error(f"無(wú)法啟動(dòng)監(jiān)控:備份目錄 {self.backup_destination.get()} 不存在且無(wú)法創(chuàng)建。")
            self.update_status(f"錯(cuò)誤: 備份目錄不存在且無(wú)法創(chuàng)建", "error")
            return

        try:
            known_drives = get_available_drives()
            logging.info(f"當(dāng)前已知驅(qū)動(dòng)器: {sorted(list(known_drives))}")
        except Exception as e_init_drives:
            logging.error(f"初始化獲取驅(qū)動(dòng)器列表失敗: {e_init_drives}")
            self.update_status(f"錯(cuò)誤: 獲取驅(qū)動(dòng)器列表失敗", "error")
            known_drives = set()

        while self.running:
            try:
                self.update_status("正在檢測(cè)驅(qū)動(dòng)器...")
                current_drives = get_available_drives()
                new_drives = current_drives - known_drives
                removed_drives = known_drives - current_drives

                if new_drives:
                    logging.info(f"檢測(cè)到新驅(qū)動(dòng)器: {sorted(list(new_drives))}")
                    for drive in new_drives:
                        if not self.running: 
                            break
                            
                        logging.info(f"等待驅(qū)動(dòng)器 {drive}: 準(zhǔn)備就緒...")
                        self.update_status(f"檢測(cè)到新驅(qū)動(dòng)器 {drive}:,等待準(zhǔn)備就緒...", "highlight")
                        
                        # 等待驅(qū)動(dòng)器準(zhǔn)備就緒
                        ready = False
                        for _ in range(5):  # 最多等待5秒
                            if not self.running:
                                break
                            try:
                                if os.path.exists(f"{drive}:\\"):
                                    ready = True
                                    break
                            except:
                                pass
                            time.sleep(1)
                        
                        if not self.running: 
                            break
                            
                        if not ready:
                            logging.warning(f"驅(qū)動(dòng)器 {drive}: 未能在5秒內(nèi)準(zhǔn)備就緒,跳過(guò)")
                            self.update_status(f"驅(qū)動(dòng)器 {drive}: 準(zhǔn)備超時(shí)", "warning")
                            continue
                            
                        try:
                            if is_removable_drive(drive):
                                self.currently_backing_up = True
                                self.backup_usb_drive(drive)
                                self.currently_backing_up = False
                            else:
                                logging.info(f"驅(qū)動(dòng)器 {drive}: 不是可移動(dòng)驅(qū)動(dòng)器,跳過(guò)備份。")
                                self.update_status(f"驅(qū)動(dòng)器 {drive}: 非U盤(pán),跳過(guò)")
                        except Exception as e_check:
                            logging.error(f"檢查或備份驅(qū)動(dòng)器 {drive}: 時(shí)出錯(cuò): {e_check}")
                            self.update_status(f"錯(cuò)誤: 處理驅(qū)動(dòng)器 {drive}: 時(shí)出錯(cuò)", "error")
                        finally:
                            if self.running:
                                self.after(3000, lambda: self.update_status("空閑,等待U盤(pán)插入..."))

                if removed_drives:
                    logging.info(f"檢測(cè)到驅(qū)動(dòng)器移除: {sorted(list(removed_drives))}")

                known_drives = current_drives

                if not new_drives and self.status_bar.cget("text").startswith("狀態(tài): 正在檢測(cè)驅(qū)動(dòng)器"):
                    self.update_status("空閑,等待U盤(pán)插入...")

                # 等待指定間隔,并允許提前退出
                interval_counter = 0
                while self.running and interval_counter < CHECK_INTERVAL:
                    time.sleep(1)
                    interval_counter += 1

            except Exception as e:
                logging.error(f"主循環(huán)發(fā)生錯(cuò)誤: {e}")
                self.update_status(f"錯(cuò)誤: {e}", "error")
                error_wait_counter = 0
                while self.running and error_wait_counter < CHECK_INTERVAL * 2:
                    time.sleep(1)
                    error_wait_counter += 1

        logging.info("后臺(tái)監(jiān)控線程已停止。")
        self.update_status("程序已停止")

    def backup_usb_drive(self, drive_letter):
        """執(zhí)行U盤(pán)備份"""
        source_drive = f"{drive_letter}:\\"
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        destination_folder = os.path.join(self.backup_destination.get(), f"Backup_{drive_letter}_{timestamp}")

        logging.info(f"檢測(cè)到U盤(pán): {source_drive}")
        self.update_status(f"檢測(cè)到U盤(pán): {drive_letter}:\\,準(zhǔn)備備份...", "highlight")
        logging.info(f"開(kāi)始備份到: {destination_folder}")
        self.update_status(f"開(kāi)始備份 {drive_letter}:\\ 到 {destination_folder}", "highlight")
        
        # 重置進(jìn)度條
        self.update_progress(0)

        start_time = time.time()
        try:
            # 獲取U盤(pán)總大小和可用空間
            try:
                total_bytes, free_bytes, _ = shutil.disk_usage(source_drive)
                total_gb = total_bytes / (1024**3)
                free_gb = free_bytes / (1024**3)
                logging.info(f"U盤(pán)總空間: {total_gb:.2f}GB, 可用空間: {free_gb:.2f}GB")
            except Exception as e_size:
                logging.warning(f"無(wú)法獲取U盤(pán)空間信息: {e_size}")

            # 計(jì)算需要備份的文件總數(shù)
            total_files = 0
            for root, dirs, files in os.walk(source_drive):
                dirs[:] = [d for d in dirs if d not in ['$RECYCLE.BIN', 'System Volume Information']]
                files[:] = [f for f in files if not f.lower().endswith(('.tmp', '.log', '.sys'))]
                total_files += len(files)
            
            logging.info(f"需要備份的文件總數(shù): {total_files}")
            
            if total_files == 0:
                logging.warning("U盤(pán)上沒(méi)有可備份的文件")
                self.update_status(f"{drive_letter}:\\ 沒(méi)有可備份的文件", "warning")
                return

            # 執(zhí)行備份
            threaded_copytree(
                source_drive, 
                destination_folder, 
                max_workers=8, 
                app_instance=self,
                total_files=total_files
            )
            
            end_time = time.time()
            duration = end_time - start_time
            logging.info(f"成功完成備份: {source_drive} -> {destination_folder} (耗時(shí): {duration:.2f} 秒)")
            self.update_status(f"備份完成: {drive_letter}:\\ (耗時(shí): {duration:.2f} 秒)", "success")
            self.update_progress(100)
            
            # 計(jì)算備份大小
            try:
                backup_size = sum(os.path.getsize(os.path.join(dirpath, filename)) 
                                for dirpath, dirnames, filenames in os.walk(destination_folder) 
                                for filename in filenames)
                backup_size_gb = backup_size / (1024**3)
                logging.info(f"備份總大小: {backup_size_gb:.2f}GB")
            except Exception as e_size:
                logging.warning(f"無(wú)法計(jì)算備份大小: {e_size}")

        except FileNotFoundError:
            logging.error(f"錯(cuò)誤:源驅(qū)動(dòng)器 {source_drive} 不存在或無(wú)法訪問(wèn)。")
            self.update_status(f"錯(cuò)誤: 無(wú)法訪問(wèn) {drive_letter}:\\", "error")
        except PermissionError:
            logging.error(f"錯(cuò)誤:沒(méi)有權(quán)限讀取 {source_drive} 或?qū)懭?{destination_folder}。")
            self.update_status(f"錯(cuò)誤: 權(quán)限不足 {drive_letter}:\\ 或目標(biāo)文件夾", "error")
        except Exception as e:
            logging.error(f"備份U盤(pán) {source_drive} 時(shí)發(fā)生未知錯(cuò)誤: {e}")
            self.update_status(f"錯(cuò)誤: 備份 {drive_letter}:\\ 時(shí)發(fā)生未知錯(cuò)誤", "error")
        finally:
            if self.running:
                self.after(5000, lambda: self.update_status("空閑,等待U盤(pán)插入..."))

    def quit_app(self):
        """退出應(yīng)用程序"""
        if self.currently_backing_up:
            if not messagebox.askyesno("確認(rèn)", "當(dāng)前正在備份中,確定要退出嗎?"):
                return
        
        logging.info("收到退出信號(hào),程序即將關(guān)閉。")
        self.running = False
        
        if hasattr(self, 'text_handler'):
            self.text_handler.stop_processing()

        if hasattr(self, 'backup_thread') and self.backup_thread and self.backup_thread.is_alive():
            try:
                self.backup_thread.join(timeout=2.0)
                if self.backup_thread.is_alive():
                    logging.warning("備份線程未能在2秒內(nèi)停止,將強(qiáng)制關(guān)閉窗口。")
            except Exception as e:
                logging.error(f"等待備份線程時(shí)出錯(cuò): {e}")

        self.destroy()

# --- 核心備份函數(shù) ---
def get_available_drives():
    """獲取當(dāng)前所有可用的驅(qū)動(dòng)器盤(pán)符"""
    drives = []
    bitmask = win32file.GetLogicalDrives()
    for letter in string.ascii_uppercase:
        if bitmask & 1:
            drives.append(letter)
        bitmask >>= 1
    return set(drives)

def is_removable_drive(drive_letter):
    """判斷指定盤(pán)符是否是可移動(dòng)驅(qū)動(dòng)器"""
    drive_path = f"{drive_letter}:\\"
    try:
        return win32file.GetDriveTypeW(drive_path) == win32file.DRIVE_REMOVABLE
    except Exception:
        return False

def should_skip_file(src, dst):
    """判斷是否需要跳過(guò)備份(增量備份邏輯)"""
    if not os.path.exists(dst):
        return False
    try:
        src_stat = os.stat(src)
        dst_stat = os.stat(dst)
        return src_stat.st_size == dst_stat.st_size and int(src_stat.st_mtime) == int(dst_stat.st_mtime)
    except Exception:
        return False

def copy_file_with_log(src, dst):
    """復(fù)制單個(gè)文件并記錄日志"""
    try:
        file_size = os.path.getsize(src)
        if file_size > 128 * 1024 * 1024:  # 大于128MB的文件使用分塊復(fù)制
            chunk_size = 16 * 1024 * 1024  # 16MB塊大小
            with open(src, 'rb') as fsrc, open(dst, 'wb') as fdst:
                while True:
                    chunk = fsrc.read(chunk_size)
                    if not chunk:
                        break
                    fdst.write(chunk)
            try:
                shutil.copystat(src, dst)  # 復(fù)制文件元數(shù)據(jù)
            except Exception as e_stat:
                logging.warning(f"無(wú)法復(fù)制元數(shù)據(jù) {src} -> {dst}: {e_stat}")
            logging.info(f"分塊復(fù)制大文件: {src} -> {dst} ({file_size/1024/1024:.2f}MB)")
        else:
            shutil.copy2(src, dst)  # 小文件直接復(fù)制
            logging.info(f"已復(fù)制: {src} -> {dst} ({file_size/1024/1024:.2f}MB)")
    except PermissionError as e_perm:
        logging.error(f"無(wú)權(quán)限復(fù)制文件 {src}: {e_perm}")
        raise
    except FileNotFoundError as e_notfound:
        logging.error(f"文件不存在 {src}: {e_notfound}")
        raise
    except Exception as e:
        logging.error(f"復(fù)制文件 {src} 時(shí)出錯(cuò): {e}")
        raise

def threaded_copytree(src, dst, skip_exts=None, skip_dirs=None, max_workers=8, app_instance=None, total_files=0):
    """線程池遞歸復(fù)制目錄"""
    if skip_exts is None:
        skip_exts = ['.tmp', '.log', '.sys']
    if skip_dirs is None:
        skip_dirs = ['$RECYCLE.BIN', 'System Volume Information']
    if not os.path.exists(dst):
        try:
            os.makedirs(dst)
        except Exception as e_mkdir:
            logging.error(f"創(chuàng)建目錄 {dst} 失敗: {e_mkdir}")
            return
    
    copied_files = 0
    tasks = []
    small_files = []
    
    try:
        with ThreadPoolExecutor(max_workers=max_workers) as executor:
            for item in os.listdir(src):
                s = os.path.join(src, item)
                d = os.path.join(dst, item)
                try:
                    if os.path.isdir(s):
                        if item in skip_dirs:
                            logging.info(f"跳過(guò)系統(tǒng)目錄: {s}")
                            continue
                        tasks.append(executor.submit(
                            threaded_copytree, s, d, skip_exts, skip_dirs, max_workers, app_instance, total_files
                        ))
                    else:
                        ext = os.path.splitext(item)[1].lower()
                        if ext in skip_exts:
                            logging.info(f"跳過(guò)系統(tǒng)文件: {s}")
                            continue
                        if should_skip_file(s, d):
                            copied_files += 1
                            if app_instance and total_files > 0:
                                progress = (copied_files / total_files) * 100
                                app_instance.update_progress(progress)
                            continue
                        if os.path.getsize(s) < 16 * 1024 * 1024:  # 小于16MB的文件批量處理
                            small_files.append((s, d))
                        else:
                            tasks.append(executor.submit(copy_file_with_log, s, d))
                except PermissionError:
                    logging.warning(f"無(wú)權(quán)限訪問(wèn): {s},跳過(guò)")
                except FileNotFoundError:
                    logging.warning(f"文件或目錄不存在: {s},跳過(guò)")
                except Exception as e_item:
                    logging.error(f"處理 {s} 時(shí)出錯(cuò): {e_item}")

            # 批量提交小文件任務(wù)
            batch_size = 16
            for i in range(0, len(small_files), batch_size):
                batch = small_files[i:i+batch_size]
                tasks.append(executor.submit(batch_copy_files, batch, app_instance, total_files, copied_files))
                copied_files += len(batch)

            # 等待所有任務(wù)完成并更新進(jìn)度
            for future in as_completed(tasks):
                try:
                    future.result()
                    if app_instance and total_files > 0:
                        copied_files += 1
                        progress = (copied_files / total_files) * 100
                        app_instance.update_progress(min(100, progress))
                except Exception as e_future:
                    logging.error(f"線程池任務(wù)出錯(cuò): {e_future}")
                    
    except PermissionError:
        logging.error(f"無(wú)權(quán)限訪問(wèn)源目錄: {src}")
        raise
    except FileNotFoundError:
        logging.error(f"源目錄不存在: {src}")
        raise
    except Exception as e_pool:
        logging.error(f"處理目錄 {src} 時(shí)線程池出錯(cuò): {e_pool}")
        raise

def batch_copy_files(file_pairs, app_instance=None, total_files=0, base_count=0):
    """批量復(fù)制小文件"""
    copied = 0
    for src, dst in file_pairs:
        try:
            copy_file_with_log(src, dst)
            copied += 1
            if app_instance and total_files > 0:
                progress = ((base_count + copied) / total_files) * 100
                app_instance.update_progress(progress)
        except Exception:
            continue

if __name__ == "__main__":
    # 創(chuàng)建并運(yùn)行主應(yīng)用
    app = App()
    app.mainloop()

十、總結(jié)與展望

本文實(shí)現(xiàn)的U盤(pán)自動(dòng)備份工具具有以下優(yōu)勢(shì):

  • 自動(dòng)化程度高:完全無(wú)需人工干預(yù)
  • 備份效率高:多線程+增量備份
  • 用戶體驗(yàn)好:直觀的可視化界面

未來(lái)擴(kuò)展方向

  • 增加云存儲(chǔ)備份支持
  • 實(shí)現(xiàn)備份數(shù)據(jù)加密
  • 添加定期自動(dòng)清理功能
  • 開(kāi)發(fā)手機(jī)端監(jiān)控APP

到此這篇關(guān)于Python實(shí)現(xiàn)自動(dòng)備份U盤(pán)內(nèi)容的文章就介紹到這了,更多相關(guān)Python自動(dòng)備份U盤(pán)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論