/
georgiy
/
work
Обзор
Документация
Войти
/
georgiy
/
work
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
task4/src/main.py
265 строк
11 KB
Georgiy Yankovskiy
task4
17 ноя 2024, 21:58
17 ноя 2024, 21:58
366fc08
Код
Авторство
О чём код?
import os import shutil import hashlib import sys import time from datetime import datetime, timedelta import logging from pathlib import Path import schedule import smtplib from email.mime.text import MIMEText from configparser import ConfigParser import logging.handlers import sqlite3 import glob def calculate_file_hash(filepath): """Вычисляет хеш MD5 файла по частям.""" hasher = hashlib.md5() with open(filepath, 'rb') as file: while True: chunk = file.read(8192) # Читаем файл по частям размером 8 КБ if not chunk: break hasher.update(chunk) return hasher.hexdigest() def init_db(db_path): """Инициализация базы данных SQLite""" conn = sqlite3.connect(db_path) cursor = conn.cursor() cursor.execute(''' CREATE TABLE IF NOT EXISTS file_hashes ( filepath TEXT PRIMARY KEY, file_hash TEXT ) ''') conn.commit() conn.close() def insert_file_hash(db_path, filepath, file_hash): """Добавление хэша файла в базу данных""" conn = sqlite3.connect(db_path) cursor = conn.cursor() cursor.execute("INSERT OR REPLACE INTO file_hashes (filepath, file_hash) VALUES (?, ?)", (filepath, file_hash)) conn.commit() conn.close() def get_file_hash(db_path, filepath): """Получение хэша файла из базы данных""" conn = sqlite3.connect(db_path) cursor = conn.cursor() cursor.execute("SELECT file_hash FROM file_hashes WHERE filepath = ?", (filepath,)) result = cursor.fetchone() conn.close() return result[0] if result else None def restore_backup(source, destination, backup_timestamp=None): """Восстанавливает данные из резервной копии.""" if backup_timestamp: backup_dir = os.path.join(source, backup_timestamp) if not os.path.exists(backup_dir): logger.error(f"Резервная копия от {backup_timestamp} не найдена.") return False else: last_full_backup_path = os.path.join(source, "last_full_backup.txt") if not os.path.exists(last_full_backup_path): logger.error("Не найден файл с датой последнего полного бекапа") return last_full_backup_time = open(last_full_backup_path, "r").read().strip() backup_dir = os.path.join(source, last_full_backup_time) logger.info(backup_dir) try: shutil.copytree(backup_dir, destination, dirs_exist_ok=True) logger.info(f"Восстановление из резервной копии выполнено в {destination}") return True except Exception as e: logger.exception(f"Ошибка восстановления: {e}") return False def get_file_list(directory): file_list = [] for root, _, files in os.walk(directory): for file in files: file_list.append(os.path.join(root, file)) return file_list def compare_files(file1, file2): with open(file1, 'rb') as f1, open(file2, 'rb') as f2: return hashlib.md5(f1.read()).hexdigest() == hashlib.md5(f2.read()).hexdigest() def backup_files(source, destination, files): time = datetime.now().strftime("%Y-%m-%d_%H-%M-%S") for file in files: relative_path = os.path.relpath(file, source) with_time = os.path.join(time, relative_path) destination_path = os.path.join(destination, with_time) logger.info(destination_path) os.makedirs(os.path.dirname(destination_path), exist_ok=True) shutil.copy2(file, destination_path) # Загрузка конфигурации config = ConfigParser() config.read('config.ini') # Настройка логирования logger = logging.getLogger(__name__) logger.setLevel(logging.DEBUG) log_handler = logging.handlers.RotatingFileHandler('backup.log', maxBytes=1024 * 1024, backupCount=5) formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') log_handler.setFormatter(formatter) logger.addHandler(log_handler) def full_backup(source, destination): time = datetime.now().strftime("%Y-%m-%d_%H-%M-%S") shutil.copytree(source, os.path.join(destination, time), ignore=shutil.ignore_patterns('.git', '__pycache__') , dirs_exist_ok=True) logger.info(f"Полное резервное копирование выполнено в {destination}") def incremental_backup(source, destination, db_path): new_files = [] modified_files = [] for root, _, files in os.walk(source): for file in files: filepath = os.path.join(root, file) file_hash = calculate_file_hash(filepath) old_hash = get_file_hash(db_path, filepath) if old_hash is None: new_files.append(filepath) elif old_hash != file_hash: modified_files.append(filepath) insert_file_hash(db_path, filepath, file_hash) # обновляем хеш в базе данных backup_files(source, destination, new_files + modified_files) logger.info( f"Инкрементальное резервное копирование выполнено в {destination}. {len(new_files)} новых файлов, " f"{len(modified_files)} измененных файлов.") def differential_backup(source, destination, db_path): modified_files = [] last_full_backup_path = os.path.join(destination, "last_full_backup.txt") if not os.path.exists(last_full_backup_path): logger.error("Не найден файл с датой последнего полного бекапа") return with open(last_full_backup_path, "r") as f: last_full_backup_time = f.read().strip() for root, _, files in os.walk(source): for file in files: filepath = os.path.join(root, file) file_hash = calculate_file_hash(filepath) old_hash = get_file_hash(db_path, filepath) if old_hash != file_hash: modified_files.append(filepath) backup_files(source, destination, modified_files) logger.info( f"Дифференциальное резервное копирование выполнено в {destination}. {len(modified_files)} измененных файлов.") def send_email(subject, body): pass # try: # smtp = smtplib.SMTP(config['email']['smtp_server'], int(config['email']['smtp_port'])) # smtp.starttls() # smtp.login(config['email']['username'], config['email']['password']) # msg = MIMEText(body) # msg['Subject'] = subject # msg['From'] = config['email']['from_address'] # msg['To'] = config['email']['to_address'] # smtp.send_message(msg) # smtp.quit() # logger.info("Email notification sent successfully") # except Exception as e: # logger.error(f"Error sending email: {e}") def cleanup_old_backups(backup_dir, retention_days): """Удаляет старые резервные копии.""" cutoff_time = datetime.now() - timedelta(days=retention_days) for backup_subdir in glob.glob(os.path.join(backup_dir, '*')): try: timestamp = datetime.strptime(os.path.basename(backup_subdir), '%Y-%m-%d_%H-%M-%S') if timestamp < cutoff_time: shutil.rmtree(backup_subdir) logger.info(f"Удалена старая резервная копия: {backup_subdir}") except (ValueError, OSError) as e: logger.warning(f"Ошибка при удалении резервной копии: {e}") def backup_job(): try: source_dir = config['backup']['source_dir'] destination_dir = config['backup']['destination_dir'] backup_method = config['backup']['method'] db_path = os.path.join(destination_dir, "backup_hashes.db") init_db(db_path) # Инициализация базы данных при первом запуске if backup_method == "full": full_backup(source_dir, destination_dir) # Обновляем хэши в базе данных после полного бэкапа for root, _, files in os.walk(source_dir): for file in files: filepath = os.path.join(root, file) file_hash = calculate_file_hash(filepath) insert_file_hash(db_path, filepath, file_hash) with open(os.path.join(destination_dir, "last_full_backup.txt"), "w") as f: f.write(datetime.now().strftime("%Y-%m-%d_%H-%M-%S")) elif backup_method == "incremental": incremental_backup(source_dir, destination_dir, db_path) elif backup_method == "differential": differential_backup(source_dir, destination_dir, db_path) else: logger.error(f"Неверный метод резервного копирования: {backup_method}") send_email("Ошибка резервного копирования", f"Неверный метод " f"резервного копирования: {backup_method}") # Очистка старых резервных копий после успешного бэкапа #retention_days = int(config['backup']['retention_days']) #cleanup_old_backups(config['backup']['destination_dir'], retention_days) except Exception as e: logger.exception(f"Ошибка резервного копирования: {e}") send_email("Ошибка резервного копирования", str(e)) # schedule.every().day.at("17:00").do(backup_job) # Пример расписания schedule.every(int(config['backup']['every_seconds'])).seconds.do(backup_job) # Пример расписания # Добавим обработку команды восстановления if __name__ == "__main__": Path(config['backup']['destination_dir']).mkdir(parents=True, exist_ok=True) Path(config['backup']['source_dir']).mkdir(parents=True, exist_ok=True) if len(sys.argv) > 1 and sys.argv[1] == "restore": if len(sys.argv) > 2: restore_time = sys.argv[2] else: restore_time = None restore_backup(config['backup']['destination_dir'], config['backup']['source_dir'], restore_time) else: # Запуск стандартного цикла бекапов while True: schedule.run_pending() time.sleep(1)