Added a new custom layout template for SEO and social media meta tags. Updated multiple documentation files with metadata for improved search engine optimization and social sharing. Enhanced Sphinx configuration with additional HTML options and meta tags.
1150 lines
34 KiB
Markdown
1150 lines
34 KiB
Markdown
---
|
|
html_meta:
|
|
description: "Comprehensive tzst examples - Learn advanced archive creation, extraction, security, and performance optimization"
|
|
keywords: "tzst examples, Python archive examples, tar.zst tutorials, Zstandard compression examples"
|
|
og:title: "tzst Examples and Tutorials"
|
|
og:description: "Comprehensive examples for tzst - archive creation, extraction, security, and performance optimization"
|
|
twitter:title: "tzst Examples and Tutorials"
|
|
twitter:description: "Comprehensive examples for tzst - archive creation, extraction, security, and performance optimization"
|
|
---
|
|
|
|
# Examples
|
|
|
|
This section provides comprehensive examples of using tzst for various scenarios and use cases.
|
|
|
|
## Table of Contents
|
|
|
|
- {ref}`basic-operations`
|
|
- {ref}`advanced-archive-creation`
|
|
- {ref}`flexible-extraction`
|
|
- {ref}`security-and-filtering`
|
|
- {ref}`performance-optimization`
|
|
- {ref}`error-handling`
|
|
- {ref}`real-world-scenarios`
|
|
- {ref}`integration-examples`
|
|
|
|
(basic-operations)=
|
|
|
|
## Basic Operations
|
|
|
|
### Creating Your First Archive
|
|
|
|
```python
|
|
from tzst import create_archive
|
|
|
|
# Simple archive creation
|
|
files_to_archive = ["document.pdf", "photos/", "config.json"]
|
|
create_archive("my-archive.tzst", files_to_archive)
|
|
|
|
# With custom compression level
|
|
create_archive("high-compression.tzst", files_to_archive, compression_level=9)
|
|
```
|
|
|
|
### Command Line Equivalent
|
|
|
|
```bash
|
|
# Create archive
|
|
tzst a my-archive.tzst document.pdf photos/ config.json
|
|
|
|
# With high compression
|
|
tzst a high-compression.tzst document.pdf photos/ config.json --compression-level 9
|
|
```
|
|
|
|
### Basic Extraction
|
|
|
|
```python
|
|
from tzst import extract_archive
|
|
|
|
# Extract to current directory
|
|
extract_archive("my-archive.tzst")
|
|
|
|
# Extract to specific directory
|
|
extract_archive("my-archive.tzst", "extracted/")
|
|
|
|
# Extract specific files only
|
|
extract_archive("my-archive.tzst", "output/", members=["document.pdf", "config.json"])
|
|
|
|
# Extract with conflict resolution
|
|
extract_archive("my-archive.tzst", "output/", conflict_resolution="skip")
|
|
```
|
|
|
|
### Listing Archive Contents
|
|
|
|
```python
|
|
from tzst import list_archive
|
|
|
|
# Simple listing
|
|
contents = list_archive("my-archive.tzst")
|
|
for item in contents:
|
|
print(f"{item['name']} ({item['size']} bytes)")
|
|
|
|
# Detailed listing with timestamps and permissions
|
|
contents = list_archive("my-archive.tzst", verbose=True)
|
|
for item in contents:
|
|
print(f"{item['name']:30} {item['size']:>10} bytes {item['mtime']}")
|
|
```
|
|
|
|
## Command Line Usage
|
|
|
|
### Archive Creation Commands
|
|
|
|
```bash
|
|
# Basic archive creation
|
|
tzst a backup.tzst documents/ photos/
|
|
|
|
# Create with specific compression level
|
|
tzst a backup.tzst documents/ photos/ -l 6
|
|
|
|
# Create from multiple sources
|
|
tzst a complete-backup.tzst /home/user/documents /home/user/photos /etc/config
|
|
|
|
# Create with verbose output
|
|
tzst a backup.tzst documents/ photos/ -v
|
|
```
|
|
|
|
### Extraction Commands
|
|
|
|
```bash
|
|
# Extract to current directory
|
|
tzst x backup.tzst
|
|
|
|
# Extract to specific directory
|
|
tzst x backup.tzst --output /restore/
|
|
|
|
# Extract specific files only
|
|
tzst x backup.tzst documents/report.pdf photos/vacation.jpg
|
|
|
|
# Extract with conflict resolution
|
|
tzst x backup.tzst --conflict-resolution skip
|
|
|
|
# Extract flattening directory structure
|
|
tzst e backup.tzst --output flat-restore/
|
|
```
|
|
|
|
### Archive Inspection Commands
|
|
|
|
```bash
|
|
# List archive contents
|
|
tzst l backup.tzst
|
|
|
|
# List with detailed information
|
|
tzst l backup.tzst --verbose
|
|
|
|
# Test archive integrity
|
|
tzst t backup.tzst
|
|
|
|
# Stream large archives efficiently
|
|
tzst l huge-archive.tzst --streaming
|
|
```
|
|
|
|
(advanced-archive-creation)=
|
|
|
|
## Advanced Archive Creation
|
|
|
|
### Working with the TzstArchive Class
|
|
|
|
```python
|
|
from tzst import TzstArchive
|
|
from pathlib import Path
|
|
|
|
# Create archive with fine-grained control
|
|
with TzstArchive("project-backup.tzst", "w", compression_level=6) as archive:
|
|
# Add individual files
|
|
archive.add("README.md")
|
|
archive.add("LICENSE")
|
|
|
|
# Add directories recursively
|
|
archive.add("src/", recursive=True)
|
|
archive.add("tests/", recursive=True)
|
|
|
|
# Add with custom archive names
|
|
archive.add("config/production.yaml", arcname="config.yaml")
|
|
archive.add("/tmp/build-info.json", arcname="build-info.json")
|
|
```
|
|
|
|
### Conditional File Addition
|
|
|
|
```python
|
|
import os
|
|
from tzst import TzstArchive
|
|
from pathlib import Path
|
|
|
|
def backup_project(project_path, output_archive):
|
|
"""Create a project backup excluding certain files."""
|
|
project_path = Path(project_path)
|
|
|
|
# Define exclusion patterns
|
|
exclude_patterns = {
|
|
"*.pyc", "*.pyo", "__pycache__",
|
|
".git", ".svn", "node_modules",
|
|
"*.tmp", "*.log", ".DS_Store"
|
|
}
|
|
|
|
with TzstArchive(output_archive, "w", compression_level=5) as archive:
|
|
for item in project_path.rglob("*"):
|
|
# Skip excluded patterns
|
|
if any(item.match(pattern) for pattern in exclude_patterns):
|
|
continue
|
|
|
|
# Skip if it's a directory (will be created automatically)
|
|
if item.is_dir():
|
|
continue
|
|
|
|
# Add file with relative path
|
|
rel_path = item.relative_to(project_path)
|
|
archive.add(str(item), arcname=str(rel_path))
|
|
print(f"Added: {rel_path}")
|
|
|
|
# Usage
|
|
backup_project("/home/user/myproject", "project-clean.tzst")
|
|
```
|
|
|
|
### Atomic Archive Creation
|
|
|
|
```python
|
|
from tzst import create_archive
|
|
|
|
# Safe atomic creation (default behavior)
|
|
# Creates in temporary file first, then moves to final location
|
|
create_archive("important-data.tzst", ["critical/"], use_temp_file=True)
|
|
|
|
# Direct creation (faster but not atomic)
|
|
create_archive("temp-data.tzst", ["temp/"], use_temp_file=False)
|
|
```
|
|
|
|
(flexible-extraction)=
|
|
|
|
## Flexible Extraction
|
|
|
|
### Extracting with Different Structures
|
|
|
|
```python
|
|
from tzst import extract_archive, TzstArchive
|
|
|
|
# Standard extraction (preserves directory structure)
|
|
extract_archive("archive.tzst", "output/")
|
|
|
|
# Flatten all files to single directory
|
|
extract_archive("archive.tzst", "flat-output/", flatten=True)
|
|
|
|
# Extract with streaming for large archives
|
|
extract_archive("huge-archive.tzst", "output/", streaming=True)
|
|
```
|
|
|
|
### Selective Extraction
|
|
|
|
```python
|
|
from tzst import TzstArchive
|
|
|
|
def extract_by_extension(archive_path, output_dir, extensions):
|
|
"""Extract only files with specific extensions."""
|
|
with TzstArchive(archive_path, "r") as archive:
|
|
members = archive.getmembers()
|
|
|
|
# Filter members by extension
|
|
filtered_members = [
|
|
member.name for member in members
|
|
if any(member.name.endswith(ext) for ext in extensions)
|
|
]
|
|
|
|
if filtered_members:
|
|
archive.extractall(output_dir, members=filtered_members)
|
|
print(f"Extracted {len(filtered_members)} files")
|
|
else:
|
|
print("No matching files found")
|
|
|
|
# Extract only images
|
|
extract_by_extension("photos.tzst", "images/", [".jpg", ".png", ".gif"])
|
|
|
|
# Extract only documents
|
|
extract_by_extension("backup.tzst", "docs/", [".pdf", ".docx", ".txt"])
|
|
```
|
|
|
|
### Custom Extraction Logic
|
|
|
|
```python
|
|
from tzst import TzstArchive
|
|
import os
|
|
|
|
def extract_large_files_only(archive_path, output_dir, min_size_mb=10):
|
|
"""Extract only files larger than specified size."""
|
|
min_size_bytes = min_size_mb * 1024 * 1024
|
|
|
|
with TzstArchive(archive_path, "r") as archive:
|
|
large_files = []
|
|
|
|
for member in archive.getmembers():
|
|
if member.isfile() and member.size > min_size_bytes:
|
|
large_files.append(member.name)
|
|
size_mb = member.size / (1024 * 1024)
|
|
print(f"Will extract: {member.name} ({size_mb:.1f} MB)")
|
|
|
|
if large_files:
|
|
os.makedirs(output_dir, exist_ok=True)
|
|
for filename in large_files:
|
|
archive.extract(filename, output_dir)
|
|
print(f"Extracted {len(large_files)} large files")
|
|
|
|
extract_large_files_only("mixed-content.tzst", "large-files/", min_size_mb=5)
|
|
```
|
|
|
|
(security-and-filtering)=
|
|
|
|
## Security and Filtering
|
|
|
|
### Safe Extraction Practices
|
|
|
|
```python
|
|
from tzst import extract_archive
|
|
|
|
# Always use secure filters (default behavior)
|
|
extract_archive("untrusted.tzst", "safe-output/", filter="data")
|
|
|
|
# For trusted archives with special tar features
|
|
extract_archive("trusted.tzst", "output/", filter="tar")
|
|
|
|
# Only for completely trusted archives
|
|
extract_archive("internal.tzst", "output/", filter="fully_trusted")
|
|
```
|
|
|
|
### Custom Security Filter
|
|
|
|
```python
|
|
import tarfile
|
|
from tzst import TzstArchive
|
|
|
|
def secure_data_filter(member, path):
|
|
"""Custom filter that only allows regular files and directories."""
|
|
# Only allow regular files and directories
|
|
if not (member.isfile() or member.isdir()):
|
|
return None
|
|
|
|
# Prevent path traversal
|
|
if os.path.isabs(member.name) or ".." in member.name:
|
|
return None
|
|
|
|
# Limit file size (100MB max)
|
|
if member.isfile() and member.size > 100 * 1024 * 1024:
|
|
return None
|
|
|
|
return member
|
|
|
|
# Use custom filter
|
|
with TzstArchive("archive.tzst", "r") as archive:
|
|
archive.extractall("secure-output/", filter=secure_data_filter)
|
|
```
|
|
|
|
### Handling File Conflicts
|
|
|
|
```python
|
|
from tzst import extract_archive, ConflictResolution
|
|
|
|
# Skip existing files
|
|
extract_archive("archive.tzst", "output/",
|
|
conflict_resolution=ConflictResolution.SKIP_ALL)
|
|
|
|
# Replace all existing files
|
|
extract_archive("archive.tzst", "output/",
|
|
conflict_resolution=ConflictResolution.REPLACE_ALL)
|
|
|
|
# Auto-rename conflicting files (adds suffix like "_1", "_2", etc.)
|
|
extract_archive("archive.tzst", "output/",
|
|
conflict_resolution=ConflictResolution.AUTO_RENAME_ALL)
|
|
|
|
# Interactive resolution (command line only)
|
|
extract_archive("archive.tzst", "output/",
|
|
conflict_resolution=ConflictResolution.ASK)
|
|
```
|
|
|
|
### Custom Conflict Resolution
|
|
|
|
```python
|
|
from tzst import extract_archive, ConflictResolution
|
|
from pathlib import Path
|
|
|
|
def custom_conflict_handler(target_path: Path) -> ConflictResolution:
|
|
"""Custom logic for handling file conflicts."""
|
|
# Check file age
|
|
if target_path.exists():
|
|
file_age_days = (time.time() - target_path.stat().st_mtime) / (24 * 3600)
|
|
|
|
if file_age_days > 30:
|
|
print(f"Replacing old file: {target_path}")
|
|
return ConflictResolution.REPLACE
|
|
else:
|
|
print(f"Keeping newer file: {target_path}")
|
|
return ConflictResolution.SKIP
|
|
|
|
return ConflictResolution.REPLACE
|
|
|
|
# Use custom callback
|
|
extract_archive("archive.tzst", "output/",
|
|
conflict_resolution=ConflictResolution.ASK,
|
|
interactive_callback=custom_conflict_handler)
|
|
```
|
|
|
|
(performance-optimization)=
|
|
|
|
## Performance Optimization
|
|
|
|
### Streaming for Large Archives
|
|
|
|
```python
|
|
from tzst import TzstArchive, list_archive, test_archive
|
|
|
|
# Memory-efficient operations for large archives
|
|
large_archive = "backup-500gb.tzst"
|
|
|
|
# Test integrity with streaming
|
|
is_valid = test_archive(large_archive, streaming=True)
|
|
|
|
# List contents with streaming
|
|
contents = list_archive(large_archive, streaming=True, verbose=True)
|
|
|
|
# Extract with streaming
|
|
with TzstArchive(large_archive, "r", streaming=True) as archive:
|
|
archive.extractall("restore/")
|
|
```
|
|
|
|
### Compression Level Optimization
|
|
|
|
```python
|
|
import time
|
|
from tzst import create_archive
|
|
|
|
def benchmark_compression_levels(files, output_prefix="test"):
|
|
"""Compare different compression levels."""
|
|
levels_to_test = [1, 3, 6, 9, 15, 22]
|
|
|
|
results = []
|
|
for level in levels_to_test:
|
|
output_file = f"{output_prefix}_level_{level}.tzst"
|
|
|
|
# Measure compression time
|
|
start_time = time.time()
|
|
create_archive(output_file, files, compression_level=level)
|
|
compress_time = time.time() - start_time
|
|
|
|
# Get file size
|
|
file_size = Path(output_file).stat().st_size
|
|
|
|
results.append({
|
|
'level': level,
|
|
'time': compress_time,
|
|
'size': file_size,
|
|
'size_mb': file_size / (1024 * 1024)
|
|
})
|
|
|
|
print(f"Level {level}: {compress_time:.2f}s, {file_size/1024/1024:.1f} MB")
|
|
|
|
return results
|
|
|
|
# Test different compression levels
|
|
results = benchmark_compression_levels(["large-directory/"])
|
|
```
|
|
|
|
### Parallel Processing
|
|
|
|
```python
|
|
import concurrent.futures
|
|
from tzst import create_archive
|
|
from pathlib import Path
|
|
|
|
def create_archive_batch(file_groups, output_dir="archives/", compression_level=6):
|
|
"""Create multiple archives in parallel."""
|
|
Path(output_dir).mkdir(exist_ok=True)
|
|
|
|
def create_single_archive(args):
|
|
group_name, files = args
|
|
output_path = Path(output_dir) / f"{group_name}.tzst"
|
|
create_archive(output_path, files, compression_level=compression_level)
|
|
return f"Created {output_path}"
|
|
|
|
# Use ThreadPoolExecutor for I/O-bound operations
|
|
with concurrent.futures.ThreadPoolExecutor(max_workers=4) as executor:
|
|
future_to_group = {
|
|
executor.submit(create_single_archive, item): item[0]
|
|
for item in file_groups.items()
|
|
}
|
|
|
|
for future in concurrent.futures.as_completed(future_to_group):
|
|
group_name = future_to_group[future]
|
|
try:
|
|
result = future.result()
|
|
print(result)
|
|
except Exception as e:
|
|
print(f"Archive {group_name} failed: {e}")
|
|
|
|
# Example usage
|
|
file_groups = {
|
|
"documents": ["docs/", "papers/"],
|
|
"projects": ["src/", "tests/"],
|
|
"media": ["photos/", "videos/"]
|
|
}
|
|
|
|
create_archive_batch(file_groups)
|
|
```
|
|
|
|
(error-handling)=
|
|
|
|
## Error Handling
|
|
|
|
### Comprehensive Error Handling
|
|
|
|
```python
|
|
from tzst import TzstArchive, TzstArchiveError, TzstDecompressionError
|
|
import logging
|
|
|
|
def safe_archive_operation(operation, *args, **kwargs):
|
|
"""Wrapper for safe archive operations with logging."""
|
|
try:
|
|
return operation(*args, **kwargs)
|
|
except TzstDecompressionError as e:
|
|
logging.error(f"Decompression error: {e}")
|
|
print("The archive appears to be corrupted or not a valid tzst file.")
|
|
return None
|
|
except TzstArchiveError as e:
|
|
logging.error(f"Archive error: {e}")
|
|
print(f"Archive operation failed: {e}")
|
|
return None
|
|
except PermissionError as e:
|
|
logging.error(f"Permission error: {e}")
|
|
print("Permission denied. Check file/directory permissions.")
|
|
return None
|
|
except FileNotFoundError as e:
|
|
logging.error(f"File not found: {e}")
|
|
print(f"File or directory not found: {e}")
|
|
return None
|
|
except Exception as e:
|
|
logging.error(f"Unexpected error: {e}")
|
|
print(f"An unexpected error occurred: {e}")
|
|
return None
|
|
|
|
# Example usage
|
|
def create_backup_safely(files, output_archive):
|
|
def create_operation():
|
|
from tzst import create_archive
|
|
return create_archive(output_archive, files)
|
|
|
|
result = safe_archive_operation(create_operation)
|
|
if result is not None:
|
|
print(f"Backup created successfully: {output_archive}")
|
|
else:
|
|
print("Backup creation failed!")
|
|
```
|
|
|
|
### Validation and Recovery
|
|
|
|
```python
|
|
from tzst import test_archive, list_archive, TzstArchive
|
|
from pathlib import Path
|
|
|
|
def validate_and_repair_archive(archive_path):
|
|
"""Validate archive and attempt basic recovery."""
|
|
archive_path = Path(archive_path)
|
|
|
|
print(f"Validating {archive_path}...")
|
|
|
|
# Test basic integrity
|
|
try:
|
|
if test_archive(archive_path):
|
|
print("✅ Archive integrity test passed")
|
|
return True
|
|
except Exception as e:
|
|
print(f"❌ Integrity test failed: {e}")
|
|
|
|
# Try to list contents
|
|
try:
|
|
contents = list_archive(archive_path)
|
|
print(f"📁 Archive contains {len(contents)} items")
|
|
|
|
# Try streaming mode if regular mode fails
|
|
contents_streaming = list_archive(archive_path, streaming=True)
|
|
if len(contents_streaming) != len(contents):
|
|
print("⚠️ Different results between modes - possible corruption")
|
|
|
|
except Exception as e:
|
|
print(f"❌ Cannot list contents: {e}")
|
|
return False
|
|
|
|
# Try partial extraction
|
|
try:
|
|
backup_dir = archive_path.parent / f"{archive_path.stem}_recovery"
|
|
backup_dir.mkdir(exist_ok=True)
|
|
|
|
with TzstArchive(archive_path, "r") as archive:
|
|
extracted_count = 0
|
|
for member in archive.getmembers():
|
|
try:
|
|
if member.isfile():
|
|
archive.extract(member.name, backup_dir)
|
|
extracted_count += 1
|
|
except Exception as e:
|
|
print(f"⚠️ Failed to extract {member.name}: {e}")
|
|
|
|
print(f"✅ Recovered {extracted_count} files to {backup_dir}")
|
|
return True
|
|
|
|
except Exception as e:
|
|
print(f"❌ Recovery failed: {e}")
|
|
return False
|
|
|
|
# Example usage
|
|
validate_and_repair_archive("potentially-corrupted.tzst")
|
|
```
|
|
|
|
(real-world-scenarios)=
|
|
|
|
## Real-World Scenarios
|
|
|
|
### Automated Backup System
|
|
|
|
```python
|
|
#!/usr/bin/env python3
|
|
"""
|
|
Daily backup script with rotation and validation.
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
from datetime import datetime, timedelta
|
|
from pathlib import Path
|
|
from tzst import create_archive, test_archive
|
|
|
|
class BackupManager:
|
|
def __init__(self, source_dirs, backup_dir, retention_days=30):
|
|
self.source_dirs = [Path(d) for d in source_dirs]
|
|
self.backup_dir = Path(backup_dir)
|
|
self.retention_days = retention_days
|
|
self.backup_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
def create_backup(self):
|
|
"""Create a new backup with timestamp."""
|
|
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
backup_name = f"backup_{timestamp}.tzst"
|
|
backup_path = self.backup_dir / backup_name
|
|
|
|
print(f"Creating backup: {backup_name}")
|
|
|
|
# Collect all existing files
|
|
files_to_backup = []
|
|
for source_dir in self.source_dirs:
|
|
if source_dir.exists():
|
|
files_to_backup.append(str(source_dir))
|
|
else:
|
|
print(f"Warning: Source directory not found: {source_dir}")
|
|
|
|
if not files_to_backup:
|
|
print("No files to backup!")
|
|
return None
|
|
|
|
try:
|
|
# Create backup with high compression for storage efficiency
|
|
create_archive(backup_path, files_to_backup, compression_level=9)
|
|
|
|
# Validate the backup
|
|
if test_archive(backup_path):
|
|
file_size = backup_path.stat().st_size / (1024 * 1024)
|
|
print(f"✅ Backup created and validated: {file_size:.1f} MB")
|
|
return backup_path
|
|
else:
|
|
print("❌ Backup validation failed!")
|
|
backup_path.unlink() # Remove invalid backup
|
|
return None
|
|
|
|
except Exception as e:
|
|
print(f"❌ Backup failed: {e}")
|
|
return None
|
|
|
|
def cleanup_old_backups(self):
|
|
"""Remove backups older than retention period."""
|
|
cutoff_date = datetime.now() - timedelta(days=self.retention_days)
|
|
|
|
removed_count = 0
|
|
for backup_file in self.backup_dir.glob("backup_*.tzst"):
|
|
# Extract timestamp from filename
|
|
try:
|
|
timestamp_str = backup_file.stem.split("_", 1)[1]
|
|
file_date = datetime.strptime(timestamp_str, "%Y%m%d_%H%M%S")
|
|
|
|
if file_date < cutoff_date:
|
|
backup_file.unlink()
|
|
removed_count += 1
|
|
print(f"Removed old backup: {backup_file.name}")
|
|
|
|
except (ValueError, IndexError):
|
|
print(f"Warning: Could not parse backup date: {backup_file.name}")
|
|
|
|
print(f"Cleaned up {removed_count} old backups")
|
|
|
|
def run_backup(self):
|
|
"""Run complete backup process."""
|
|
print("Starting backup process...")
|
|
|
|
backup_path = self.create_backup()
|
|
if backup_path:
|
|
self.cleanup_old_backups()
|
|
print("Backup process completed successfully!")
|
|
return True
|
|
else:
|
|
print("Backup process failed!")
|
|
return False
|
|
|
|
# Configuration
|
|
if __name__ == "__main__":
|
|
# Customize these paths for your setup
|
|
BACKUP_SOURCES = [
|
|
"~/Documents",
|
|
"~/Projects",
|
|
"~/Pictures",
|
|
"/etc", # System configs (Linux/macOS)
|
|
]
|
|
|
|
BACKUP_DESTINATION = "~/Backups"
|
|
RETENTION_DAYS = 30
|
|
|
|
# Expand user paths
|
|
sources = [os.path.expanduser(path) for path in BACKUP_SOURCES]
|
|
destination = os.path.expanduser(BACKUP_DESTINATION)
|
|
|
|
# Run backup
|
|
backup_manager = BackupManager(sources, destination, RETENTION_DAYS)
|
|
success = backup_manager.run_backup()
|
|
|
|
sys.exit(0 if success else 1)
|
|
```
|
|
|
|
### Log File Archiver
|
|
|
|
```python
|
|
#!/usr/bin/env python3
|
|
"""
|
|
Archive and compress log files by date.
|
|
"""
|
|
|
|
import re
|
|
from datetime import datetime, timedelta
|
|
from pathlib import Path
|
|
from tzst import create_archive
|
|
|
|
def archive_logs_by_date(log_dir, archive_dir, days_old=7):
|
|
"""Archive log files older than specified days."""
|
|
log_dir = Path(log_dir)
|
|
archive_dir = Path(archive_dir)
|
|
archive_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
cutoff_date = datetime.now() - timedelta(days=days_old)
|
|
|
|
# Group log files by date
|
|
log_groups = {}
|
|
log_pattern = re.compile(r"(\d{4}-\d{2}-\d{2})")
|
|
|
|
for log_file in log_dir.glob("*.log"):
|
|
# Try to extract date from filename or modification time
|
|
date_match = log_pattern.search(log_file.name)
|
|
if date_match:
|
|
file_date_str = date_match.group(1)
|
|
try:
|
|
file_date = datetime.strptime(file_date_str, "%Y-%m-%d")
|
|
except ValueError:
|
|
# Fall back to modification time
|
|
file_date = datetime.fromtimestamp(log_file.stat().st_mtime)
|
|
else:
|
|
# Use modification time
|
|
file_date = datetime.fromtimestamp(log_file.stat().st_mtime)
|
|
|
|
# Skip recent files
|
|
if file_date >= cutoff_date:
|
|
continue
|
|
|
|
# Group by date
|
|
date_key = file_date.strftime("%Y-%m-%d")
|
|
if date_key not in log_groups:
|
|
log_groups[date_key] = []
|
|
log_groups[date_key].append(log_file)
|
|
|
|
# Create archives for each date group
|
|
archived_files = []
|
|
for date_key, files in log_groups.items():
|
|
archive_name = f"logs_{date_key}.tzst"
|
|
archive_path = archive_dir / archive_name
|
|
|
|
# Skip if archive already exists
|
|
if archive_path.exists():
|
|
print(f"Archive already exists: {archive_name}")
|
|
continue
|
|
|
|
print(f"Archiving {len(files)} log files for {date_key}")
|
|
|
|
try:
|
|
# Create archive with maximum compression (logs compress well)
|
|
create_archive(archive_path, [str(f) for f in files], compression_level=22)
|
|
|
|
# Verify archive
|
|
from tzst import test_archive
|
|
if test_archive(archive_path):
|
|
# Remove original files after successful archiving
|
|
for log_file in files:
|
|
log_file.unlink()
|
|
archived_files.append(log_file)
|
|
|
|
file_size = archive_path.stat().st_size / 1024
|
|
print(f"✅ Created {archive_name} ({file_size:.1f} KB)")
|
|
else:
|
|
print(f"❌ Archive validation failed for {archive_name}")
|
|
archive_path.unlink()
|
|
|
|
except Exception as e:
|
|
print(f"❌ Failed to archive logs for {date_key}: {e}")
|
|
|
|
print(f"Archived {len(archived_files)} log files")
|
|
|
|
# Usage
|
|
if __name__ == "__main__":
|
|
archive_logs_by_date("/var/log", "/var/archives", days_old=7)
|
|
```
|
|
|
|
### Data Migration Tool
|
|
|
|
```python
|
|
#!/usr/bin/env python3
|
|
"""
|
|
Migrate data between systems using tzst archives.
|
|
"""
|
|
|
|
import hashlib
|
|
from pathlib import Path
|
|
from tzst import create_archive, extract_archive, test_archive
|
|
|
|
class DataMigrator:
|
|
def __init__(self, source_dir, staging_dir):
|
|
self.source_dir = Path(source_dir)
|
|
self.staging_dir = Path(staging_dir)
|
|
self.staging_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
def calculate_checksum(self, file_path):
|
|
"""Calculate SHA256 checksum of a file."""
|
|
sha256_hash = hashlib.sha256()
|
|
with open(file_path, "rb") as f:
|
|
for chunk in iter(lambda: f.read(4096), b""):
|
|
sha256_hash.update(chunk)
|
|
return sha256_hash.hexdigest()
|
|
|
|
def create_migration_package(self, package_name):
|
|
"""Create a migration package with checksums."""
|
|
package_path = self.staging_dir / f"{package_name}.tzst"
|
|
checksum_file = self.staging_dir / f"{package_name}.sha256"
|
|
|
|
print(f"Creating migration package: {package_name}")
|
|
|
|
# Create the archive
|
|
create_archive(
|
|
package_path,
|
|
[str(self.source_dir)],
|
|
compression_level=6 # Balanced for network transfer
|
|
)
|
|
|
|
# Verify archive
|
|
if not test_archive(package_path):
|
|
raise RuntimeError("Archive validation failed")
|
|
|
|
# Calculate and save checksum
|
|
checksum = self.calculate_checksum(package_path)
|
|
with open(checksum_file, "w") as f:
|
|
f.write(f"{checksum} {package_path.name}\n")
|
|
|
|
package_size = package_path.stat().st_size / (1024 * 1024)
|
|
print(f"✅ Package created: {package_size:.1f} MB")
|
|
print(f"📋 Checksum: {checksum}")
|
|
|
|
return package_path, checksum_file
|
|
|
|
def verify_and_extract_package(self, package_path, checksum_path, destination):
|
|
"""Verify package integrity and extract."""
|
|
package_path = Path(package_path)
|
|
checksum_path = Path(checksum_path)
|
|
destination = Path(destination)
|
|
|
|
print(f"Verifying package: {package_path.name}")
|
|
|
|
# Verify checksum
|
|
expected_checksum = checksum_path.read_text().strip().split()[0]
|
|
actual_checksum = self.calculate_checksum(package_path)
|
|
|
|
if expected_checksum != actual_checksum:
|
|
raise RuntimeError(f"Checksum mismatch! Expected: {expected_checksum}, Got: {actual_checksum}")
|
|
|
|
print("✅ Checksum verification passed")
|
|
|
|
# Test archive integrity
|
|
if not test_archive(package_path):
|
|
raise RuntimeError("Archive integrity check failed")
|
|
|
|
print("✅ Archive integrity verified")
|
|
|
|
# Extract with conflict resolution
|
|
destination.mkdir(parents=True, exist_ok=True)
|
|
extract_archive(
|
|
package_path,
|
|
destination,
|
|
conflict_resolution="replace_all" # Overwrite for migration
|
|
)
|
|
|
|
print(f"✅ Package extracted to: {destination}")
|
|
|
|
# Example usage
|
|
if __name__ == "__main__":
|
|
# Create migration package
|
|
migrator = DataMigrator("/home/user/important-data", "/tmp/migration")
|
|
package_path, checksum_path = migrator.create_migration_package("data-migration-v1")
|
|
|
|
# Simulate transfer and extraction on target system
|
|
migrator.verify_and_extract_package(
|
|
package_path,
|
|
checksum_path,
|
|
"/home/user/restored-data"
|
|
)
|
|
```
|
|
|
|
(integration-examples)=
|
|
|
|
## Integration Examples
|
|
|
|
### Django Management Command
|
|
|
|
```python
|
|
# management/commands/backup_media.py
|
|
from django.core.management.base import BaseCommand
|
|
from django.conf import settings
|
|
from tzst import create_archive
|
|
from datetime import datetime
|
|
import os
|
|
|
|
class Command(BaseCommand):
|
|
help = 'Create a backup of media files'
|
|
|
|
def add_arguments(self, parser):
|
|
parser.add_argument(
|
|
'--output-dir',
|
|
default='/backups',
|
|
help='Output directory for backup files'
|
|
)
|
|
parser.add_argument(
|
|
'--compression-level',
|
|
type=int,
|
|
default=6,
|
|
help='Compression level (1-22)'
|
|
)
|
|
|
|
def handle(self, *args, **options):
|
|
media_root = settings.MEDIA_ROOT
|
|
output_dir = options['output_dir']
|
|
compression_level = options['compression_level']
|
|
|
|
if not os.path.exists(media_root):
|
|
self.stdout.write(
|
|
self.style.ERROR(f'Media directory not found: {media_root}')
|
|
)
|
|
return
|
|
|
|
# Create backup filename with timestamp
|
|
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
|
|
backup_filename = f'media_backup_{timestamp}.tzst'
|
|
backup_path = os.path.join(output_dir, backup_filename)
|
|
|
|
# Ensure output directory exists
|
|
os.makedirs(output_dir, exist_ok=True)
|
|
|
|
try:
|
|
self.stdout.write(f'Creating media backup: {backup_filename}')
|
|
create_archive(backup_path, [media_root], compression_level=compression_level)
|
|
|
|
# Verify backup
|
|
from tzst import test_archive
|
|
if test_archive(backup_path):
|
|
file_size = os.path.getsize(backup_path) / (1024 * 1024)
|
|
self.stdout.write(
|
|
self.style.SUCCESS(
|
|
f'Backup created successfully: {backup_filename} ({file_size:.1f} MB)'
|
|
)
|
|
)
|
|
else:
|
|
self.stdout.write(
|
|
self.style.ERROR('Backup validation failed!')
|
|
)
|
|
|
|
except Exception as e:
|
|
self.stdout.write(
|
|
self.style.ERROR(f'Backup failed: {e}')
|
|
)
|
|
```
|
|
|
|
### Flask Application Integration
|
|
|
|
```python
|
|
from flask import Flask, request, send_file, jsonify
|
|
from tzst import create_archive, extract_archive
|
|
import tempfile
|
|
import os
|
|
from pathlib import Path
|
|
|
|
app = Flask(__name__)
|
|
|
|
@app.route('/api/backup', methods=['POST'])
|
|
def create_backup():
|
|
"""API endpoint to create backups."""
|
|
try:
|
|
data = request.get_json()
|
|
paths = data.get('paths', [])
|
|
compression_level = data.get('compression_level', 6)
|
|
|
|
if not paths:
|
|
return jsonify({'error': 'No paths specified'}), 400
|
|
|
|
# Create temporary archive
|
|
with tempfile.NamedTemporaryFile(suffix='.tzst', delete=False) as tmp:
|
|
temp_path = tmp.name
|
|
|
|
create_archive(temp_path, paths, compression_level=compression_level)
|
|
|
|
# Return archive file
|
|
return send_file(
|
|
temp_path,
|
|
as_attachment=True,
|
|
download_name='backup.tzst',
|
|
mimetype='application/octet-stream'
|
|
)
|
|
|
|
except Exception as e:
|
|
return jsonify({'error': str(e)}), 500
|
|
finally:
|
|
# Clean up temporary file
|
|
if 'temp_path' in locals() and os.path.exists(temp_path):
|
|
os.unlink(temp_path)
|
|
|
|
@app.route('/api/extract', methods=['POST'])
|
|
def extract_files():
|
|
"""API endpoint to extract archives."""
|
|
try:
|
|
if 'file' not in request.files:
|
|
return jsonify({'error': 'No file uploaded'}), 400
|
|
|
|
file = request.files['file']
|
|
if file.filename == '':
|
|
return jsonify({'error': 'No file selected'}), 400
|
|
|
|
# Save uploaded file temporarily
|
|
with tempfile.NamedTemporaryFile(suffix='.tzst', delete=False) as tmp:
|
|
temp_archive = tmp.name
|
|
file.save(temp_archive)
|
|
|
|
# Create extraction directory
|
|
extract_dir = tempfile.mkdtemp()
|
|
|
|
# Extract archive
|
|
extract_archive(temp_archive, extract_dir)
|
|
|
|
# List extracted files
|
|
extracted_files = []
|
|
for root, dirs, files in os.walk(extract_dir):
|
|
for file in files:
|
|
rel_path = os.path.relpath(os.path.join(root, file), extract_dir)
|
|
extracted_files.append(rel_path)
|
|
|
|
return jsonify({
|
|
'success': True,
|
|
'extracted_files': extracted_files,
|
|
'extract_path': extract_dir
|
|
})
|
|
|
|
except Exception as e:
|
|
return jsonify({'error': str(e)}), 500
|
|
finally:
|
|
# Clean up temporary archive
|
|
if 'temp_archive' in locals() and os.path.exists(temp_archive):
|
|
os.unlink(temp_archive)
|
|
|
|
if __name__ == '__main__':
|
|
app.run(debug=True)
|
|
```
|
|
|
|
### Jupyter Notebook Integration
|
|
|
|
```python
|
|
# Cell 1: Setup
|
|
import pandas as pd
|
|
from tzst import create_archive, extract_archive, list_archive
|
|
from pathlib import Path
|
|
import matplotlib.pyplot as plt
|
|
|
|
# Cell 2: Create dataset archive
|
|
def archive_datasets(data_dir="./data", archive_name="datasets.tzst"):
|
|
"""Archive all dataset files for sharing."""
|
|
data_path = Path(data_dir)
|
|
|
|
if not data_path.exists():
|
|
print(f"Creating sample data directory: {data_dir}")
|
|
data_path.mkdir(exist_ok=True)
|
|
|
|
# Create sample datasets
|
|
sample_data = pd.DataFrame({
|
|
'A': range(100),
|
|
'B': range(100, 200),
|
|
'C': range(200, 300)
|
|
})
|
|
|
|
sample_data.to_csv(data_path / "sample.csv", index=False)
|
|
sample_data.to_parquet(data_path / "sample.parquet")
|
|
|
|
# Create archive
|
|
create_archive(archive_name, [str(data_path)], compression_level=9)
|
|
|
|
# Show archive contents
|
|
contents = list_archive(archive_name, verbose=True)
|
|
df = pd.DataFrame(contents)
|
|
|
|
print(f"Created archive: {archive_name}")
|
|
return df
|
|
|
|
# Execute
|
|
archive_contents = archive_datasets()
|
|
display(archive_contents)
|
|
|
|
# Cell 3: Analyze archive
|
|
def analyze_archive(archive_path="datasets.tzst"):
|
|
"""Analyze archive contents and compression."""
|
|
contents = list_archive(archive_path, verbose=True)
|
|
df = pd.DataFrame(contents)
|
|
|
|
# File type analysis
|
|
df['extension'] = df['name'].str.split('.').str[-1]
|
|
file_types = df.groupby('extension')['size'].agg(['count', 'sum']).reset_index()
|
|
|
|
# Visualization
|
|
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))
|
|
|
|
# File count by type
|
|
ax1.bar(file_types['extension'], file_types['count'])
|
|
ax1.set_title('File Count by Type')
|
|
ax1.set_xlabel('File Extension')
|
|
ax1.set_ylabel('Count')
|
|
|
|
# Size by type
|
|
ax2.bar(file_types['extension'], file_types['sum'] / 1024) # KB
|
|
ax2.set_title('Total Size by Type (KB)')
|
|
ax2.set_xlabel('File Extension')
|
|
ax2.set_ylabel('Size (KB)')
|
|
|
|
plt.tight_layout()
|
|
plt.show()
|
|
|
|
return df, file_types
|
|
|
|
# Execute
|
|
contents_df, file_summary = analyze_archive()
|
|
print("File Summary:")
|
|
display(file_summary)
|
|
```
|
|
|
|
These examples demonstrate the flexibility and power of tzst for various real-world scenarios. The library's clean API and robust error handling make it suitable for everything from simple backup scripts to complex enterprise applications.
|