# File Backup Utility

This utility creates a timestamped ZIP archive of a specified directory. It demonstrates how to use `zipfile` for granular control over archiving (like filtering files) and `shutil` for high-level file operations (like copying to a redundancy drive and checking disk space).

**Modules Used:**
*   [[programming/python/modules/zipfile-module|zipfile]]: To create the compressed archive.
*   [[programming/python/modules/shutil-module|shutil]]: To copy the backup to a second location and check disk usage.
*   [[programming/python/modules/argparse-module|argparse]]: To handle command-line arguments.
*   [[programming/python/modules/pathlib-module|pathlib]]: For object-oriented filesystem paths.

## The Code

Save this as `backup.py`.

```python
import zipfile
import shutil
import argparse
import os
from datetime import datetime
from pathlib import Path

def create_backup(source, dest, redundancy_dest=None):
    source_path = Path(source).resolve()
    dest_path = Path(dest).resolve()
    
    if not source_path.exists():
        print(f"Error: Source '{source_path}' not found.")
        return

    # 1. Setup Paths and Timestamp
    timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
    archive_name = f"backup_{source_path.name}_{timestamp}.zip"
    archive_path = dest_path / archive_name
    
    dest_path.mkdir(parents=True, exist_ok=True)
    
    # 2. Create Zip Archive
    # We use zipfile instead of shutil.make_archive to have control over what gets included
    print(f"Zipping '{source_path}' to '{archive_path}'...")
    
    try:
        with zipfile.ZipFile(archive_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
            for root, _, files in os.walk(source_path):
                for file in files:
                    file_path = Path(root) / file
                    
                    # Example Filter: Skip temporary files
                    if file_path.suffix in ['.tmp', '.log']:
                        continue
                    
                    # Store relative path in zip to avoid full system paths
                    arcname = file_path.relative_to(source_path)
                    zipf.write(file_path, arcname)
        print("Backup created successfully.")
    except Exception as e:
        print(f"Failed to create zip: {e}")
        return

    # 3. Redundancy Copy
    if redundancy_dest:
        redundancy_path = Path(redundancy_dest).resolve()
        redundancy_path.mkdir(parents=True, exist_ok=True)
        
        print(f"Copying to redundancy location: {redundancy_path}...")
        try:
            shutil.copy2(archive_path, redundancy_path)
            print("Redundancy copy successful.")
        except Exception as e:
            print(f"Redundancy copy failed: {e}")

    # 4. Check Disk Usage
    total, used, free = shutil.disk_usage(dest_path)
    print(f"Free space remaining on destination: {free // (2**30)} GiB")

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Backup Utility")
    parser.add_argument("source", help="Folder to backup")
    parser.add_argument("dest", help="Folder to save backup zip")
    parser.add_argument("--redundancy", help="Optional second location for backup copy")
    
    args = parser.parse_args()
    create_backup(args.source, args.dest, args.redundancy)
```

## Usage

```bash
# Basic backup
python backup.py ./my_project ./backups

# Backup with redundancy copy
python backup.py ./my_project ./backups --redundancy /mnt/external_drive
```

[[programming/python/python]]