Voici un programme Python de backup entièrement gratuit. J’en avais royalement marre de télécharger des programmes qui ne faisaient pas bien leur job, alors je m’y suis collé.
Un programme Python de backup: cahier des charges
Ce que je voulais:
- Une interface graphique épurée
- Pas de milliers d’options qui ne servent finalement à rien
- Une sauvegarde incrémentielle (ne sauvegarde que les nouveaux fichiers)
- Une sauvegarde des répertoires à sauvegarder et de la destination
- Une lecture automatique des répertoires à sauvegarder dès le lancement du programme
- La possibilité de supprimer un dossier si on ne veut plus l’inclure dans la sauvegarde
- Une barre de progression, parce que bon! Il ne faut pas exagéré non plus… 🙂
Voici donc l’interface graphique:
Personnellement, je n’ai ps besoin de plus…
Les programmes de backup
J’ai essayé au fil des années plusieurs logiciels gratuits. Et bien qu’ils fussent faciles à utiliser pendant un certain temps, ils plantaient après.
Le dernier en date est Fbackup 9. J’en étais content jusqu’à ce que, d’un coup, il ne fasse plus les sauvegardes correctement. Il mettait toujours un message d’erreur comme quoi le chemin gnagna n’existait pas… Insupportable! C’en était trop! Il me fallait réagir! Je n’allais tout de même pas me laisser faire par une machine… non mais!
Le programme Python de backup que je vous propose
J’ai jeté mon dévolu sur Tkinter pour l’interface graphique. C’est tout de même plus simple que Qt par exemple. Et en plus, c’est inclus dans les distribution Python.
J’ai aussi opté pour la POO. J’aime bien… 🙂
Alors, plutôt que de longs discours (que personne ne lit de toute façon), voici le programme:
# -*- coding: utf-8 -*- """ Created on Sat Jun 29 15:47:22 2024 @author: Stéphane Pasquet """ import tkinter as tk from tkinter import filedialog, messagebox from tkinter import ttk import shutil import os import json class BackupApp: def __init__(self, root): self.root = root self.root.title("Programme de Backup") self.source_paths = [] self.destination_path = tk.StringVar() self.create_widgets() self.load_paths() def create_widgets(self): tk.Label(self.root, text="Source:").grid(row=0, column=0, padx=10, pady=10) self.source_listbox = tk.Listbox(self.root, selectmode=tk.MULTIPLE, height=10, width=50) self.source_listbox.grid(row=0, column=1, rowspan=2, padx=10, pady=10) tk.Button(self.root, text="Ajouter Dossier", command=self.browse_source).grid(row=0, column=2, padx=10, pady=10) tk.Button(self.root, text="Supprimer Dossier", command=self.remove_selected_sources).grid(row=1, column=2, padx=10, pady=10) tk.Label(self.root, text="Destination:").grid(row=2, column=0, padx=10, pady=10) tk.Entry(self.root, textvariable=self.destination_path).grid(row=2, column=1, padx=10, pady=10) tk.Button(self.root, text="Parcourir", command=self.browse_destination).grid(row=2, column=2, padx=10, pady=10) tk.Button(self.root, text="Démarrer Backup", command=self.start_backup).grid(row=3, column=1, padx=10, pady=10) self.progress = ttk.Progressbar(self.root, orient="horizontal", length=400, mode="determinate") self.progress.grid(row=4, column=0, columnspan=3, padx=10, pady=10) def browse_source(self): sources = filedialog.askdirectory(mustexist=True) if sources: self.source_paths.append(sources) self.source_listbox.insert(tk.END, sources) self.save_paths() def browse_destination(self): destination = filedialog.askdirectory(mustexist=True) if destination: self.destination_path.set(destination) self.save_paths() def remove_selected_sources(self): selected_indices = self.source_listbox.curselection() selected_paths = [self.source_listbox.get(i) for i in selected_indices] for path in selected_paths: self.source_paths.remove(path) for i in selected_indices[::-1]: self.source_listbox.delete(i) self.save_paths() def start_backup(self): destination = self.destination_path.get() if not self.source_paths or not destination: messagebox.showerror("Erreur", "Veuillez sélectionner les répertoires source et destination.") return total_files = sum([len(files) for source in self.source_paths for _, _, files in os.walk(source)]) self.progress["maximum"] = total_files self.progress["value"] = 0 try: for source in self.source_paths: self.incremental_backup(source, destination) messagebox.showinfo("Succès", "Sauvegarde incrémentielle terminée avec succès.") except Exception as e: messagebox.showerror("Erreur", f"Une erreur est survenue: {e}") def incremental_backup(self, source, destination): for root, dirs, files in os.walk(source): relative_path = os.path.relpath(root, source) dest_dir = os.path.join(destination, os.path.basename(source), relative_path) if not os.path.exists(dest_dir): os.makedirs(dest_dir) for file in files: source_file = os.path.join(root, file) dest_file = os.path.join(dest_dir, file) if not os.path.exists(dest_file) or os.path.getmtime(source_file) > os.path.getmtime(dest_file): shutil.copy2(source_file, dest_file) self.progress["value"] += 1 self.root.update_idletasks() def save_paths(self): data = { "source_paths": self.source_paths, "destination_path": self.destination_path.get() } with open("paths.json", "w") as file: json.dump(data, file) def load_paths(self): if os.path.exists("paths.json"): with open("paths.json", "r") as file: data = json.load(file) self.source_paths = data.get("source_paths", []) self.destination_path.set(data.get("destination_path", "")) for path in self.source_paths: self.source_listbox.insert(tk.END, path) if __name__ == "__main__": root = tk.Tk() app = BackupApp(root) root.mainloop()
J’ai été très agréablement surpris par la rapidité à laquelle il a fait sa première sauvegarde, bien que plutôt costaude! Et les sauvegardes ultérieures, n’en parlons pas!
Vraiment, j’aime beaucoup ce programme, alors je le partage avec vous. Qu’en pensez-vous ?
Cela fonctionne bien sous MacOS, merci pour ce programme !