Code source de auto_save_projet.sauve_projet_qualite_air

#!/usr/bin/env python3
"""
Version: 1.1.0 (2024-01-12)
Auteur: kahina et franck - Groupe 2
Contact: bigmoletos@yopmail.com

Script de sauvegarde automatique avec compression ZIP.

Ce module:
- Crée une archive ZIP datée du projet
- Génère un résumé hiérarchique des modifications
- Vérifie l'intégrité de la sauvegarde

Architecture:
------------
1. Sauvegarde:
   - Compression ZIP du projet avec niveau de compression optimal
   - Organisation par date/heure (format: JJ_MM_AAAA_HHhMM)
   - Vérification d'intégrité avec test ZIP

2. Logs:
   - Résumé hiérarchique des modifications avec emojis
   - Classification par dossier avec indentation
   - Horodatage des changements au format ISO

3. Structure des fichiers:
   - backup_dir/
     ├── projet_qualite_air.zip (archive complète)
     └── resume_des_modifs.log (modifications détaillées)

4. Gestion des erreurs:
   - Validation des chemins source/destination
   - Vérification des permissions
   - Contrôle d'intégrité ZIP
   - Logging détaillé des erreurs

Dépendances:
-----------
    - Python 3.8+
    - zipfile: Compression et vérification
    - pathlib: Gestion des chemins
    - logging: Journalisation des événements
"""

import os
import sys
import time
import shutil
import zipfile
import logging
from pathlib import Path
from datetime import datetime, timedelta

# Configuration du logging avec rotation des fichiers
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    filename='sauvegarde.log',
    encoding='utf-8'
)
logger = logging.getLogger(__name__)

# Ajouter un handler pour la console
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
console_handler.setFormatter(formatter)
logger.addHandler(console_handler)

[docs] def create_backup_folder(): """ Crée le dossier de sauvegarde avec horodatage. Cette fonction: 1. Génère un nom de dossier unique avec date et heure 2. Crée l'arborescence si nécessaire 3. Vérifie les permissions d'écriture Format du dossier: ----------------- JJ_MM_AAAA_HHhMM (exemple: 12_01_2024_15h30) Returns: Path: Chemin du dossier de sauvegarde créé Raises: OSError: Si la création du dossier échoue PermissionError: Si les droits sont insuffisants """ try: # Chemin racine des sauvegardes backup_root = Path(r"J:\sauve_AJC_formation_oct2024") # Génération du nom avec horodatage current_time = datetime.now() folder_name = current_time.strftime("%d_%m_%Y_%Hh%M") backup_dir = backup_root / folder_name # Création du dossier avec vérification backup_dir.mkdir(parents=True, exist_ok=True) # Test d'écriture test_file = backup_dir / ".write_test" test_file.touch() test_file.unlink() logger.info(f"Dossier de sauvegarde créé: {backup_dir}") return backup_dir except PermissionError as e: logger.error(f"Permissions insuffisantes pour créer {backup_dir}: {e}") raise except OSError as e: logger.error(f"Erreur lors de la création du dossier {backup_dir}: {e}") raise
[docs] def log_file_modification(file_path, action="modifié"): """ Enregistre une modification de fichier dans le log. Args: file_path (Path): Chemin du fichier modifié action (str): Type de modification (modifié, créé, supprimé) """ try: rel_path = file_path.relative_to(Path("C:/AJC_projets/projet_qualite_air")) logger.info(f"📄 Fichier {action}: {rel_path}") except ValueError: logger.warning(f"Fichier hors projet: {file_path}")
[docs] def generate_changes_summary(project_path, backup_dir): """ Génère un résumé des fichiers modifiés dans les 3 dernières heures. """ try: summary_file = backup_dir / "resume_des_modifs.log" three_hours_ago = datetime.now() - timedelta(hours=3) logger.info("🔍 Analyse des modifications récentes...") modifications_found = False with summary_file.open('w', encoding='utf-8') as f: f.write("=== RÉSUMÉ DES MODIFICATIONS ===\n") f.write(f"Date: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n") f.write("================================\n\n") for root, dirs, files in os.walk(project_path): root_path = Path(root) rel_path = root_path.relative_to(project_path) # Filtrer les fichiers modifiés modified_files = [] for file in files: file_path = root_path / file try: mtime = datetime.fromtimestamp(file_path.stat().st_mtime) if mtime > three_hours_ago: modified_files.append((file, mtime)) log_file_modification(file_path) modifications_found = True except (OSError, PermissionError): continue # Écrire les modifications dans le résumé if modified_files: indent = " " * len(rel_path.parts) f.write(f"\n{indent}📁 {rel_path}\n") modified_files.sort(key=lambda x: x[1], reverse=True) for file, mtime in modified_files: f.write(f"{indent} 📄 {file}\n") f.write(f"{indent} └─ Modifié le {mtime.strftime('%Y-%m-%d %H:%M:%S')}\n") if not modifications_found: msg = "Aucune modification détectée dans les 3 dernières heures" f.write(f"\n{msg}\n") logger.info(f"ℹ️ {msg}") return summary_file except Exception as e: logger.error(f"❌ Erreur lors de la génération du résumé: {e}") raise
[docs] def create_zip_backup(project_path, backup_dir): """ Crée une archive ZIP optimisée du projet. """ try: zip_path = backup_dir / "projet_qualite_air.zip" logger.info("Début de la compression ZIP...") with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED, compresslevel=1) as zipf: # Utiliser un niveau de compression plus rapide (1 au lieu de 6) for root, dirs, files in os.walk(project_path): root_path = Path(root) # Exclure les dossiers de cache et environnements virtuels dirs[:] = [d for d in dirs if not d.startswith(('.', '__pycache__', 'venv'))] for file in files: if file.startswith('.') or file.endswith(('.pyc', '.pyo')): continue file_path = root_path / file try: arcname = file_path.relative_to(project_path) zipf.write(file_path, arcname) except (OSError, PermissionError): continue logger.info(f"Sauvegarde ZIP créée avec succès: {zip_path}") return zip_path except Exception as e: logger.error(f"Erreur lors de la création du ZIP: {e}") raise
[docs] def verify_zip_integrity(zip_path): """ Vérifie l'intégrité de l'archive ZIP. Cette fonction: 1. Teste la structure de l'archive 2. Vérifie chaque fichier compressé 3. Valide le CRC des données 4. Contrôle la présence du manifeste Args: zip_path (Path): Chemin de l'archive à vérifier Returns: bool: True si l'archive est valide et complète Notes: - Utilise zipfile.testzip() pour la vérification - Vérifie aussi la présence du manifeste - Contrôle la taille minimale attendue """ try: # Vérifier que le fichier existe et n'est pas vide if not zip_path.exists() or zip_path.stat().st_size == 0: logger.error(f"Archive ZIP invalide ou vide: {zip_path}") return False with zipfile.ZipFile(zip_path, 'r') as zipf: # Tester l'intégrité globale result = zipf.testzip() if result is not None: logger.error(f"Fichier corrompu dans l'archive: {result}") return False # Vérifier la présence du manifeste if "manifest.txt" not in zipf.namelist(): logger.error("Manifeste manquant dans l'archive") return False # Vérifier que l'archive contient des fichiers if len(zipf.namelist()) < 2: # manifeste + au moins 1 fichier logger.error("Archive ne contenant pas assez de fichiers") return False logger.info("Vérification de l'archive ZIP réussie") return True except zipfile.BadZipFile as e: logger.error(f"Archive ZIP corrompue: {e}") return False except Exception as e: logger.error(f"Erreur lors de la vérification du ZIP: {e}") return False
[docs] def main(): """ Fonction principale de sauvegarde. Crée une sauvegarde datée avec un résumé des modifications et une archive ZIP. """ try: logger.info("Démarrage de la sauvegarde...") # Créer le dossier de sauvegarde daté backup_dir = create_backup_folder() logger.info(f"Dossier de sauvegarde créé: {backup_dir}") # Chemin du projet à sauvegarder project_path = Path("C:/AJC_projets/projet_qualite_air") if not project_path.exists(): raise FileNotFoundError(f"Dossier projet non trouvé: {project_path}") # Générer le résumé des modifications récentes logger.info("Génération du résumé des modifications des 3 dernières heures...") generate_changes_summary(project_path, backup_dir) # Créer l'archive ZIP logger.info("Création de l'archive ZIP...") zip_path = create_zip_backup(project_path, backup_dir) # Vérifier l'intégrité de la sauvegarde logger.info("Vérification de l'intégrité...") if verify_zip_integrity(zip_path): logger.info("✅ Sauvegarde terminée avec succès") else: logger.error("❌ Erreur d'intégrité de la sauvegarde") except Exception as e: logger.error(f"Erreur lors de la sauvegarde: {e}") raise
if __name__ == "__main__": main()