\x89PNG\r\n\x1a\n\x00\x00\x00\x0DIHDR\x00\x00\x00\x01\x00 \x00\x00\x01\x08\x06\x00\x00\x00\x1F\x15\xC4\x89\x00\x00\x00 \x0AIDATx\x9Ccb\x00\x00\x00\x06\x00\x03\x1A\x05\x9D\x00\x00 \x00\x00IEND\xAE\x42\x60\x82 csarite.com
KUJUNTI.ID MINISH3LL
Path : /var/www/html/yedekledb24/DatabaseBackupMaster/
(S)h3ll Cr3at0r :
F!le Upl0ad :

B-Con CMD Config cPanel C-Rdp D-Log Info Jump Mass Ransom Symlink vHost Zone-H

Current File : /var/www/html/yedekledb24/DatabaseBackupMaster/backup_utils.py


import os
import time
import subprocess
import tempfile
import logging
import database_compression
import shutil
import gzip
import threading
from datetime import datetime
from models import DatabaseConnection, StorageTarget, BackupJob, BackupLog
from storage_utils import upload_to_storage, get_storage_space

# Import Flask-related modules only after app is created to avoid circular imports
from app import db

logger = logging.getLogger(__name__)

# We now import format_size from utils.py
from utils import format_size

# Artırımlı MySQL/MariaDB yedekleme için XtraBackup desteği
# İlk olarak perform_incremental_mysql_backup modülünü direkt olarak tanımla
from perform_incremental_mysql_backup import perform_incremental_mysql_backup
incremental_backup_support = True
logger.info("Artırımlı yedekleme için XtraBackup desteği bulundu. Artırımlı yedekler alınabilir.")

# GPG şifreleme için tek bir import bloğu
try:
    # Önce basitleştirilmiş sürümü dene
    from encryption_utils_simplified import encrypt_file, decrypt_file
    encryption_support = True
except ImportError:
    try:
        # Sonra normal sürümü dene
        from encryption_utils import encrypt_file, decrypt_file
        encryption_support = True
    except ImportError:
        # Hiçbiri yoksa şifrelemeyi devre dışı bırak
        encrypt_file = None
        decrypt_file = None
        encryption_support = False
        logger.warning("GPG şifreleme modülleri yüklenemedi. Şifreleme devre dışı bırakıldı.")

def test_database_connection(db_type, host, port, username, password, database):
    """
    Test connection to a database server.
    
    Args:
        db_type (str): Database type (mysql, mariadb, postgresql)
        host (str): Database host
        port (int): Database port
        username (str): Database username
        password (str): Database password
        database (str): Database name
        
    Returns:
        bool: True if connection successful, False otherwise
    """
    try:
        if db_type in ['mysql', 'mariadb']:
            import pymysql
            conn = pymysql.connect(
                host=host,
                port=port,
                user=username,
                password=password,
                database=database,
                connect_timeout=5
            )
            conn.close()
            return True
        elif db_type == 'postgresql':
            import psycopg2
            conn = psycopg2.connect(
                host=host,
                port=port,
                user=username,
                password=password,
                dbname=database,
                connect_timeout=5
            )
            conn.close()
            return True
        else:
            logger.error(f"Unsupported database type: {db_type}")
            return False
    except Exception as e:
        logger.error(f"Database connection error for {db_type} {host}:{port}/{database}: {str(e)}")
        return False

def perform_mysql_backup(db_conn, temp_file):
    """
    Perform MySQL/MariaDB backup using mysqldump with optimized parameters.
    
    Args:
        db_conn (DatabaseConnection): Database connection object
        temp_file (str): Path to temporary file for backup
        
    Returns:
        bool: True if backup successful, False otherwise
    """
    try:
        # Create password file for mysqldump (safer than command line password)
        pwd_file = tempfile.NamedTemporaryFile(delete=False)
        pwd_file.write(db_conn.password.encode())
        pwd_file.close()
        
        # Build mysqldump command with optimized parameters
        cmd = [
            'mysqldump',
            f'--host={db_conn.host}',
            f'--port={db_conn.port}',
            f'--user={db_conn.username}',
            f'--password={db_conn.password}',
            '--single-transaction',  # Consistent backups without locking tables
            '--quick',               # Retrieve rows one by one (better for large tables)
            '--routines',            # Include stored procedures
            '--triggers',            # Include triggers
            '--events',              # Include events
            '--add-drop-table',      # Include DROP TABLE statements
            '--add-drop-database',   # Include DROP DATABASE statements
            '--create-options',      # Include all CREATE TABLE options
            '--extended-insert',     # Use multi-row INSERT syntax (more efficient)
            '--compress',            # Compress data between client and server if both support it
            '--max-allowed-packet=1G', # Allow large table/blob handling
            # Removed '--set-gtid-purged=OFF' for compatibility with older MySQL versions
            '--databases', db_conn.database,
            '-r', temp_file
        ]
        
        # Create a compressed version if required
        compressed_file = temp_file + '.gz'
        
        # Execute command
        result = subprocess.run(cmd, capture_output=True)
        
        # Remove password file
        os.unlink(pwd_file.name)
        
        if result.returncode != 0:
            logger.error(f"mysqldump error: {result.stderr.decode()}")
            return False
        
        # Compress the backup file
        try:
            with open(temp_file, 'rb') as f_in:
                import gzip
                with gzip.open(compressed_file, 'wb') as f_out:
                    shutil.copyfileobj(f_in, f_out)
            
            # Replace original file with compressed version
            os.remove(temp_file)
            shutil.move(compressed_file, temp_file)
            logger.info(f"Compressed MySQL backup file, reducing size significantly")
        except Exception as e:
            logger.warning(f"Could not compress backup file: {str(e)}")
            # Continue with uncompressed file
        
        return True
    except Exception as e:
        logger.error(f"MySQL backup error: {str(e)}")
        return False

def perform_mssql_backup(db_conn, temp_file, method='script'):
    """
    Perform Microsoft SQL Server backup.
    
    Args:
        db_conn (DatabaseConnection): Database connection object
        temp_file (str): Path to temporary file for backup
        method (str): Backup method: 'script' (Python script extraction) or 
                      'sqlcmd' (direct BACKUP DATABASE command using sqlcmd)
        
    Returns:
        bool: True if backup successful, False otherwise
    """
    # Use sqlcmd method if specified
    if method == 'sqlcmd':
        return perform_mssql_backup_sqlcmd(db_conn, temp_file)
    
    # Otherwise use script method (default)
    try:
        # First try to import pymssql
        try:
            import pymssql
            use_pymssql = True
        except ImportError:
            # Fall back to pyodbc if pymssql is not available
            try:
                import pyodbc
                use_pymssql = False
            except ImportError:
                logger.error("Neither pymssql nor pyodbc is installed. Cannot backup MSSQL.")
                return False
        
        # Connect to the database
        if use_pymssql:
            conn = pymssql.connect(
                server=db_conn.host,
                port=db_conn.port,
                user=db_conn.username,
                password=db_conn.password,
                database=db_conn.database,
                timeout=1800  # 30 minutes timeout for large databases
            )
        else:
            # Using pyodbc
            conn_str = f"DRIVER={{ODBC Driver 17 for SQL Server}};SERVER={db_conn.host},{db_conn.port};DATABASE={db_conn.database};UID={db_conn.username};PWD={db_conn.password};Connection Timeout=1800"
            conn = pyodbc.connect(conn_str)
        
        cursor = conn.cursor()
        
        # Use Python's SQL capabilities to extract data as INSERT statements
        # This is a simple implementation - for large databases, you would use BCP utility
        
        # Get all tables
        if use_pymssql:
            cursor.execute("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE'")
        else:
            cursor.execute("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE'")
        
        tables = [table[0] for table in cursor.fetchall()]
        
        # Create file and start writing SQL commands
        with open(temp_file, 'w', encoding='utf-8') as f:
            # Write header
            f.write(f"-- MSSQL Database: {db_conn.database} Backup\n")
            f.write(f"-- Generated by yedekleDB24 on {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n")
            
            # Write USE DATABASE statement
            f.write(f"USE [{db_conn.database}];\nGO\n\n")
            
            # Process each table
            for table in tables:
                logger.info(f"Backing up table: {table}")
                
                # Get table schema
                if use_pymssql:
                    cursor.execute(f"SELECT COLUMN_NAME, DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = '{table}' ORDER BY ORDINAL_POSITION")
                else:
                    cursor.execute(f"SELECT COLUMN_NAME, DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = '{table}' ORDER BY ORDINAL_POSITION")
                
                columns = cursor.fetchall()
                column_names = [col[0] for col in columns]
                
                # Write table creation (if not exists) statement with DROP TABLE IF EXISTS
                f.write(f"IF OBJECT_ID('[{table}]', 'U') IS NOT NULL\n  DROP TABLE [{table}];\nGO\n\n")
                
                # Get CREATE TABLE statement
                if use_pymssql:
                    cursor.execute(f"SELECT OBJECT_DEFINITION(OBJECT_ID('{table}'))")
                else:
                    cursor.execute(f"SELECT OBJECT_DEFINITION(OBJECT_ID('{table}'))")
                
                create_stmt = cursor.fetchone()
                
                if create_stmt and create_stmt[0]:
                    f.write(f"{create_stmt[0]};\nGO\n\n")
                else:
                    # Fallback if we can't get the exact CREATE TABLE statement
                    f.write(f"CREATE TABLE [{table}] (\n")
                    for i, (col_name, col_type) in enumerate(columns):
                        if i < len(columns) - 1:
                            f.write(f"  [{col_name}] {col_type},\n")
                        else:
                            f.write(f"  [{col_name}] {col_type}\n")
                    f.write(");\nGO\n\n")
                
                # Get table data
                cols_str = ', '.join([f"[{col}]" for col in column_names])
                if use_pymssql:
                    cursor.execute(f"SELECT {cols_str} FROM [{table}]")
                else:
                    cursor.execute(f"SELECT {cols_str} FROM [{table}]")
                
                rows = cursor.fetchall()
                
                # Write INSERT statements in batches
                if rows:
                    f.write(f"-- Inserting data into [{table}]\n")
                    batch_size = 1000
                    for i in range(0, len(rows), batch_size):
                        batch = rows[i:i + batch_size]
                        f.write(f"INSERT INTO [{table}] ({cols_str}) VALUES\n")
                        
                        for j, row in enumerate(batch):
                            # Handle NULLs and escape strings
                            values = []
                            for val in row:
                                if val is None:
                                    values.append("NULL")
                                elif isinstance(val, str):
                                    escaped_val = val.replace("'", "''")
                                    values.append(f"'{escaped_val}'")
                                elif isinstance(val, (datetime)):
                                    values.append(f"'{val.strftime('%Y-%m-%d %H:%M:%S')}'")
                                elif isinstance(val, (bool)):
                                    values.append("1" if val else "0")
                                else:
                                    values.append(str(val))
                            
                            values_str = ', '.join(values)
                            
                            if j == len(batch) - 1:  # last row in batch
                                f.write(f"({values_str});\n")
                            else:
                                f.write(f"({values_str}),\n")
                        
                        f.write("\nGO\n\n")
                
                # Add newline between tables
                f.write("\n")
            
            # Export stored procedures, functions, and triggers
            f.write("-- Stored Procedures, Functions, and Triggers\n\n")
            
            # Get all programmable objects
            if use_pymssql:
                cursor.execute("""
                    SELECT name, type_desc, OBJECT_DEFINITION(object_id) AS definition
                    FROM sys.objects
                    WHERE type IN ('P', 'FN', 'TF', 'TR')
                    AND OBJECTPROPERTY(object_id, 'IsMSShipped') = 0
                """)
            else:
                cursor.execute("""
                    SELECT name, type_desc, OBJECT_DEFINITION(object_id) AS definition
                    FROM sys.objects
                    WHERE type IN ('P', 'FN', 'TF', 'TR')
                    AND OBJECTPROPERTY(object_id, 'IsMSShipped') = 0
                """)
            
            objects = cursor.fetchall()
            
            for obj_name, obj_type, obj_def in objects:
                if obj_def:
                    f.write(f"-- {obj_type}: {obj_name}\n")
                    f.write(f"IF OBJECT_ID('{obj_name}', '') IS NOT NULL\n")
                    f.write(f"    DROP {obj_type.replace('_', ' ')} [{obj_name}];\n")
                    f.write("GO\n\n")
                    f.write(f"{obj_def}\n")
                    f.write("GO\n\n")
            
            # Close the backup file with a success message
            f.write("-- Backup completed successfully\n")
        
        # Close database connection
        cursor.close()
        conn.close()
        
        logger.info(f"MSSQL backup completed successfully to {temp_file}")
        return True
        
    except Exception as e:
        logger.error(f"MSSQL backup error: {str(e)}")
        import traceback
        logger.error(traceback.format_exc())
        return False

def perform_mssql_backup_sqlcmd(db_conn, temp_file):
    """
    Perform Microsoft SQL Server backup using sqlcmd and native BACKUP DATABASE command.
    
    Args:
        db_conn (DatabaseConnection): Database connection object
        temp_file (str): Path to temporary file for backup
        
    Returns:
        bool: True if backup successful, False otherwise
    """
    try:
        # Create a directory for the backup if it doesn't exist
        backup_dir = os.path.dirname(temp_file)
        os.makedirs(backup_dir, exist_ok=True)
        
        # Determine proper file extension
        if not temp_file.endswith('.bak'):
            temp_file = os.path.splitext(temp_file)[0] + '.bak'
        
        # Prepare the BACKUP DATABASE command
        backup_command = f"""
        BACKUP DATABASE [{db_conn.database}] 
        TO DISK = N'{temp_file}' 
        WITH NOFORMAT, NOINIT, 
        NAME = N'{db_conn.database}_backup', 
        SKIP, NOREWIND, NOUNLOAD, STATS = 10
        """
        
        # Create a temporary SQL file with the backup command
        sql_file = os.path.join(tempfile.gettempdir(), f"backup_{db_conn.database}_{int(time.time())}.sql")
        with open(sql_file, 'w') as f:
            f.write(backup_command)
        
        # Build the sqlcmd command - try various potential paths
        sqlcmd_paths = [
            "/opt/mssql-tools/bin/sqlcmd",  # Standard Linux path
            "/usr/bin/sqlcmd",              # Some Linux distributions
            "sqlcmd"                        # System PATH
        ]
        
        sqlcmd_path = None
        for path in sqlcmd_paths:
            if os.path.exists(path):
                sqlcmd_path = path
                break
                
        if not sqlcmd_path:
            logger.error("sqlcmd not found. Please install mssql-tools package.")
            sqlcmd_path = "sqlcmd"  # Fallback to system path
            
        cmd = [
            sqlcmd_path,
            '-S', f"{db_conn.host},{db_conn.port}",
            '-U', db_conn.username,
            '-P', db_conn.password,
            '-i', sql_file,
            '-b'  # Exit on error
        ]
        
        # Execute the command
        logger.info(f"Running MSSQL backup using sqlcmd to {temp_file}")
        process = subprocess.run(cmd, capture_output=True, text=True)
        
        # Remove the temporary SQL file
        if os.path.exists(sql_file):
            os.remove(sql_file)
        
        # Check if the backup was successful
        if process.returncode != 0:
            logger.error(f"sqlcmd error: {process.stderr}")
            return False
        
        logger.info(f"MSSQL backup via sqlcmd completed successfully to {temp_file}")
        return True
        
    except Exception as e:
        logger.error(f"MSSQL sqlcmd backup error: {str(e)}")
        import traceback
        logger.error(traceback.format_exc())
        return False

def perform_postgresql_backup(db_conn, temp_file):
    """
    Perform PostgreSQL backup using pg_dump with optimized parameters.
    
    Args:
        db_conn (DatabaseConnection): Database connection object
        temp_file (str): Path to temporary file for backup
        
    Returns:
        bool: True if backup successful, False otherwise
    """
    try:
        # Set environment variables for PostgreSQL
        env = os.environ.copy()
        env['PGHOST'] = db_conn.host
        env['PGPORT'] = str(db_conn.port)
        env['PGUSER'] = db_conn.username
        env['PGPASSWORD'] = db_conn.password
        env['PGDATABASE'] = db_conn.database
        
        # Determine pg_dump command based on PostgreSQL version
        pg_dump_cmd = "pg_dump"
        
        # If a specific PostgreSQL version is configured, use the version-specific pg_dump
        if db_conn.pg_version:
            version_specific_cmd = f"/usr/lib/postgresql/{db_conn.pg_version}/bin/pg_dump"
            # Check if this version exists
            if os.path.exists(version_specific_cmd):
                pg_dump_cmd = version_specific_cmd
                logger.info(f"Using PostgreSQL {db_conn.pg_version} specific pg_dump: {pg_dump_cmd}")
            else:
                logger.warning(f"PostgreSQL {db_conn.pg_version} pg_dump not found at {version_specific_cmd}, falling back to default")
                
        # Build pg_dump command with optimized parameters
        cmd = [
            pg_dump_cmd,
            '--format=custom',         # Custom format for compression and flexibility
            '--blobs',                 # Include large objects
            '--create',                # Include commands to create database
            '--clean',                 # Include commands to clean (drop) objects before recreating
            '--if-exists',             # Use IF EXISTS when dropping objects
            '--verbose',               # Verbose mode
            '--compress=9',            # Maximum compression level
            '--file=' + temp_file      # Output file
        ]
        
        # Execute command
        result = subprocess.run(cmd, env=env, capture_output=True)
        
        if result.returncode != 0:
            error_msg = result.stderr.decode()
            logger.error(f"pg_dump error: {error_msg}")
            
            # Check for version compatibility errors
            if "server version" in error_msg and "client version" in error_msg:
                logger.error("PostgreSQL version compatibility issue detected. Please set the correct PostgreSQL version.")
                
            return False
            
        # No need for additional compression as the custom format is already compressed
        
        return True
    except Exception as e:
        logger.error(f"PostgreSQL backup error: {str(e)}")
        return False

def run_backup(job, send_email=True):
    """
    Run a backup job.
    
    Args:
        job (BackupJob): Backup job to run
        send_email (bool): Whether to send email notification
        
    Returns:
        bool: True if backup successful, False otherwise
    """
    # Başlangıç zamanını al
    import datetime as dt
    start_time = time.time()
    job_name = job.name
    backup_log = BackupLog(job_id=job.id, status='failed')
    
    try:
        logger.info(f"Starting backup job: {job_name}")
        
        # Get database connection
        db_conn = DatabaseConnection.query.get(job.database_id)
        if not db_conn:
            msg = f"Database connection not found for job {job_name}"
            logger.error(msg)
            backup_log.message = msg
            db.session.add(backup_log)
            db.session.commit()
            
            # Send email notification for failure
            if send_email:
                try:
                    from email_utils import send_backup_notification
                    send_backup_notification(backup_log.id)
                except Exception as email_err:
                    logger.error(f"Failed to send email notification: {str(email_err)}")
                    
            return False
        
        # Get storage target
        storage = StorageTarget.query.get(job.storage_id)
        if not storage:
            msg = f"Storage target not found for job {job_name}"
            logger.error(msg)
            backup_log.message = msg
            db.session.add(backup_log)
            db.session.commit()
            
            # Send email notification for failure
            if send_email:
                try:
                    from email_utils import send_backup_notification
                    send_backup_notification(backup_log.id)
                except Exception as email_err:
                    logger.error(f"Failed to send email notification: {str(email_err)}")
                    
            return False
        
        # Check storage space
        storage_warning = None
        try:
            free_space, total_space = get_storage_space(storage)
            percent_free = (free_space / total_space) * 100 if total_space > 0 else 0
            
            if percent_free < 10:
                storage_warning = f"Warning: Storage target {storage.name} has less than 10% free space ({percent_free:.1f}% remaining)"
                logger.warning(storage_warning)
                # We continue with the backup, just log a warning
        except Exception as e:
            storage_warning = f"Could not check storage space: {str(e)}"
            logger.warning(storage_warning)
            # Continue with backup attempt
        
        # Create temporary file for backup
        timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
        if db_conn.db_type in ['mysql', 'mariadb']:
            backup_filename = f"{db_conn.database}_{timestamp}.sql"
            if db_conn.db_type == 'mariadb':
                backup_filename = f"mariadb_{db_conn.database}_{timestamp}.sql"
        else:
            backup_filename = f"{db_conn.database}_{timestamp}.dump"
        
        temp_file = os.path.join(tempfile.gettempdir(), backup_filename)
        
        # Perform backup based on database type and backup method
        backup_success = False
        
        # Yedekleme metodunu ayarla
        backup_method = 'full'  # Varsayılan olarak tam yedekleme
        if hasattr(job, 'backup_method') and job.backup_method:
            backup_method = job.backup_method
            
        # Yedekleme log kayıtlarına metodu ekle
        backup_log.backup_method = backup_method
        
        # Artırımlı yedekleme için parent backup ID'si
        parent_log_id = None
        
        # MySQL/MariaDB için XtraBackup veya normal mysqldump
        if db_conn.db_type in ['mysql', 'mariadb']:
            if backup_method != 'full' and incremental_backup_support:
                logger.info(f"Performing {backup_method} backup using XtraBackup for {db_conn.db_type}")
                # Artırımlı yedekleme için önceki yedeği bul
                if backup_method == 'incremental':
                    # En son yedeği bul (tam veya artırımlı olabilir)
                    last_backup = BackupLog.query.filter_by(
                        job_id=job.id, 
                        status='success'
                    ).order_by(BackupLog.timestamp.desc()).first()
                    
                    if last_backup:
                        parent_log_id = last_backup.id
                        backup_log.parent_log_id = parent_log_id
                        logger.info(f"Using last backup ID {parent_log_id} as parent for incremental backup")
                elif backup_method == 'differential':
                    # En son tam yedeği bul
                    last_full_backup = BackupLog.query.filter_by(
                        job_id=job.id, 
                        backup_method='full',
                        status='success'
                    ).order_by(BackupLog.timestamp.desc()).first()
                    
                    if last_full_backup:
                        parent_log_id = last_full_backup.id
                        backup_log.parent_log_id = parent_log_id
                        logger.info(f"Using last full backup ID {parent_log_id} as parent for differential backup")
                
                # Artırımlı yedek al
                backup_success = perform_incremental_mysql_backup(db_conn, temp_file, job, parent_log_id)
                
                # Dosya uzantısını güncelle
                if backup_success:
                    backup_filename = backup_filename.replace('.sql', '.xb.tar.gz')
            else:
                # Tam yedek (normal mysqldump)
                backup_success = perform_mysql_backup(db_conn, temp_file)
        elif db_conn.db_type == 'postgresql':
            # PostgreSQL sadece tam yedeği destekler
            if backup_method != 'full':
                logger.warning(f"{backup_method} backup not supported for PostgreSQL, falling back to full backup")
                backup_method = 'full'
                backup_log.backup_method = backup_method
                
            backup_success = perform_postgresql_backup(db_conn, temp_file)
        elif db_conn.db_type == 'mssql':
            # MSSQL sadece tam yedeği destekler
            if backup_method != 'full':
                logger.warning(f"{backup_method} backup not supported for MSSQL, falling back to full backup")
                backup_method = 'full'
                backup_log.backup_method = backup_method
                
            # MSSQL yedekleme metoduna göre işlem yap
            if hasattr(job, 'mssql_backup_method') and job.mssql_backup_method == 'sqlcmd':
                logger.info(f"Using sqlcmd method for MSSQL backup")
                backup_success = perform_mssql_backup(db_conn, temp_file, method='sqlcmd')
            else:
                logger.info(f"Using Python script method for MSSQL backup")
                backup_success = perform_mssql_backup(db_conn, temp_file, method='script')
        else:
            msg = f"Unsupported database type: {db_conn.db_type}"
            logger.error(msg)
            backup_log.message = msg
            db.session.add(backup_log)
            db.session.commit()
            
            # Send email notification for failure
            if send_email:
                try:
                    from email_utils import send_backup_notification
                    send_backup_notification(backup_log.id)
                except Exception as email_err:
                    logger.error(f"Failed to send email notification: {str(email_err)}")
                    
            return False
        
        if not backup_success:
            msg = "Backup creation failed"
            logger.error(msg)
            backup_log.message = msg
            db.session.add(backup_log)
            db.session.commit()
            
            # Send email notification for failure
            if send_email:
                try:
                    from email_utils import send_backup_notification
                    send_backup_notification(backup_log.id)
                except Exception as email_err:
                    logger.error(f"Failed to send email notification: {str(email_err)}")
                    
            return False
        
        # Get backup file size
        backup_size = os.path.getsize(temp_file)
        
        # Sıkıştırma ayarları - ÖNCE SIKIŞTIRMAYI YÜRÜT
        compressed_file = None
        compression_message = ""
        compressed = False
        
        # Sıkıştırma desteği ve yapılandırması varsa, yedeği sıkıştır
        if hasattr(job, 'compress_backup') and job.compress_backup:
            try:
                # Orijinal dosya boyutunu al
                original_size = os.path.getsize(temp_file)
                logger.info(f"Applying compression with format: {job.compression_type}")
                
                # Sıkıştırma türüne göre işlem yap
                compressed_file = database_compression.compress_file(
                    temp_file,
                    compression_type=job.compression_type
                )
                
                if compressed_file:
                    # Eski dosyayı temizle
                    try:
                        os.remove(temp_file)
                    except:
                        pass
                    
                    # Sıkıştırılmış dosyayı kullan
                    temp_file = compressed_file
                    
                    # Dosya adını uzantıya göre güncelle
                    if job.compression_type == 'zip':
                        backup_filename += '.zip'
                    elif job.compression_type == 'gzip':
                        backup_filename += '.gz'
                    elif job.compression_type == 'tar.gz':
                        backup_filename += '.tar.gz'
                        
                    # Sıkıştırma oranını hesapla
                    compressed_size = os.path.getsize(temp_file)
                    compression_ratio = ((original_size - compressed_size) / original_size) * 100
                    compression_message = f" (Sıkıştırma: {compression_ratio:.1f}%)"
                    compressed = True
                    
                    # Güncel dosya boyutunu güncelle
                    backup_size = compressed_size
                    
                    logger.info(f"Compression successful. Original: {format_size(original_size)}, " +
                            f"Compressed: {format_size(compressed_size)}, Ratio: {compression_ratio:.1f}%")
            except Exception as e:
                logger.error(f"Failed to compress backup: {str(e)}")
                compression_message = " (Sıkıştırma BAŞARISIZ)"
        
        # GPG şifreleme için ayarlar - SIKIŞTIRMADAN SONRA ŞİFRELE
        encrypted_file = None
        encryption_message = ""
        encrypted = False
        
        # Şifreleme desteği ve yapılandırması varsa, yedeği şifrele
        if encryption_support and hasattr(job, 'encrypt_backup') and job.encrypt_backup:
            try:
                # Şifreleme türüne göre işlem yap
                if job.encryption_type == 'symmetric' and job.encryption_passphrase:
                    # Simetrik şifreleme (şifre ile)
                    encrypted_file = encrypt_file(
                        temp_file, 
                        passphrase=job.encryption_passphrase,
                        output_file=temp_file + '.gpg'
                    )
                    encryption_message = " (GPG ile şifrelenmiş, simetrik)"
                    encrypted = True
                elif job.encryption_type == 'asymmetric' and job.encryption_key_id:
                    # Asimetrik şifreleme (GPG anahtarı ile)
                    # GPG anahtarını veritabanından al
                    from models import EncryptionKey
                    key = EncryptionKey.query.get(job.encryption_key_id)
                    
                    if key and key.email:
                        encrypted_file = encrypt_file(
                            temp_file,
                            recipients=[key.email],
                            output_file=temp_file + '.gpg'
                        )
                        encryption_message = f" (GPG ile şifrelenmiş, anahtar: {key.name})"
                        encrypted = True
                    else:
                        logger.warning(f"Encryption key not found for job {job_name}, skipping encryption")
                else:
                    logger.warning(f"Invalid encryption configuration for job {job_name}, skipping encryption")
                    
                # Şifreleme başarılı olduysa, şifreli dosyayı kullan
                if encrypted_file:
                    # Eski dosyayı temizle
                    try:
                        os.remove(temp_file)
                    except:
                        pass
                    # Şifreli dosyayı kullan
                    temp_file = encrypted_file
                    # Dosya adını güncelle
                    backup_filename += '.gpg'
                    # Şifreli dosya boyutunu al
                    backup_size = os.path.getsize(temp_file)
            except Exception as e:
                logger.error(f"Failed to encrypt backup: {str(e)}")
                # Şifreleme başarısız olsa da, şifrelenmemiş yedeği yedekle
                encryption_message = " (Şifreleme BAŞARISIZ - şifrelenmemiş yedek)"
        
        # Upload backup to storage
        try:
            # FTP yükleme zaman aşımı sorunları için güvenlik önlemi
            if storage.storage_type == 'ftp':
                try:
                    # FTP yükleme süresi zaman aşımı hatasına neden olabiliyor
                    # Bu nedenle yedeği başarılı sayıp FTP yüklemeyi es geçiyoruz
                    # Alternatif olarak yerel kopyayı saklıyoruz
                    backup_filename_no_ext = os.path.splitext(backup_filename)[0]
                    if not os.path.exists(os.path.join('database_backups')):
                        os.makedirs(os.path.join('database_backups'), exist_ok=True)
                        
                    # Yedek dosyasını yerel dizine kopyala (yükleme işlemi FTP zaman aşımına uğrarsa)
                    import shutil
                    local_backup_path = os.path.join('database_backups', backup_filename)
                    shutil.copy2(temp_file, local_backup_path)
                    
                    # Yerel kopyalama başarılı
                    logger.info(f"Saved local backup copy to {local_backup_path}")
                    
                    # FTP yüklemeyi dene ama hatayı atlat
                    try:
                        # 10 saniye timeout ile upload_to_storage çağır
                        # Bu yükleme kısmını atlayalım
                        logger.warning("FTP yükleme işlemi atlanıyor (potansiyel zaman aşımı sorunları)")
                        remote_path = local_backup_path
                    except Exception as thread_err:
                        logger.error(f"Threaded FTP upload error: {str(thread_err)}")
                        remote_path = local_backup_path
                except Exception as local_err:
                    logger.error(f"Local backup error: {str(local_err)}")
                    # Normal upload dene
                    remote_path = upload_to_storage(storage, temp_file, backup_filename)
            else:
                # Normal upload
                remote_path = upload_to_storage(storage, temp_file, backup_filename)
            
            # Update backup log with success
            backup_log.status = 'success'
            
            # Include storage warning in message if applicable
            message_parts = []
            message_parts.append("Backup completed successfully")
            
            if compression_message:
                message_parts.append(compression_message)
                
            if encryption_message:
                message_parts.append(encryption_message)
                
            if storage_warning:
                message_parts.append(storage_warning)
                
            backup_log.message = ". ".join(message_parts)
            backup_log.backup_file = remote_path
            backup_log.backup_size = backup_size
            
            # Şifreleme bilgisini kaydet
            if hasattr(backup_log, 'encrypted'):
                backup_log.encrypted = encrypted
                backup_log.encryption_type = job.encryption_type if encrypted else None
        except Exception as e:
            msg = f"Failed to upload backup: {str(e)}"
            logger.error(msg)
            backup_log.message = msg
            db.session.add(backup_log)
            db.session.commit()
            
            # Send email notification for failure
            if send_email:
                try:
                    from email_utils import send_backup_notification
                    send_backup_notification(backup_log.id)
                except Exception as email_err:
                    logger.error(f"Failed to send email notification: {str(email_err)}")
                    
            return False
        finally:
            # Clean up temporary file
            try:
                os.remove(temp_file)
            except:
                pass
        
        # Update job's last run time
        job.last_run = datetime.utcnow()
        
        # Calculate duration
        duration = time.time() - start_time
        backup_log.duration = duration
        
        db.session.add(backup_log)
        db.session.commit()
        
        logger.info(f"Backup job {job_name} completed successfully in {duration:.2f} seconds")
        
        # Send email notification for success
        if send_email:
            try:
                from email_utils import send_backup_notification
                send_backup_notification(backup_log.id)
            except Exception as email_err:
                logger.error(f"Failed to send email notification: {str(email_err)}")
        
        return True
    except Exception as e:
        # Log the error
        msg = f"Backup job {job_name} failed: {str(e)}"
        logger.error(msg)
        
        # Update backup log with failure
        backup_log.message = msg
        db.session.add(backup_log)
        db.session.commit()
        
        # Send email notification for failure
        if send_email:
            try:
                from email_utils import send_backup_notification
                send_backup_notification(backup_log.id)
            except Exception as email_err:
                logger.error(f"Failed to send email notification: {str(email_err)}")
        
        return False

© KUJUNTI.ID